From 7e81e0e1e7fc89fb990fda0e3a813bc73d994238 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Mon, 3 Jun 2024 15:57:53 +0300 Subject: [PATCH] feature: goldstein: keyword-assign-from: add --- README.md | 12 ++ packages/goldstein/index.spec.js | 30 +++++ packages/goldstein/parser.js | 8 ++ .../fixture/add-array.gs | 1 + .../fixture/assign-from.gs | 1 + packages/internal-parse-maybe-assign/index.js | 114 ++++++++++++++++++ .../internal-parse-maybe-assign/index.spec.js | 20 +++ .../keyword-add-array/fixture/assign-from.gs | 1 + .../keyword-add-array/fixture/assign-from.js | 1 + packages/keyword-add-array/fixture/const.gs | 5 + packages/keyword-add-array/index.js | 109 ++--------------- .../keyword-assign-from/fixture/add-array.gs | 1 + .../fixture/assign-from.gs | 1 + .../fixture/assign-from.js | 1 + packages/keyword-assign-from/fixture/const.gs | 5 + packages/keyword-assign-from/index.js | 26 ++++ packages/keyword-assign-from/index.spec.js | 26 ++++ 17 files changed, 264 insertions(+), 98 deletions(-) create mode 100644 packages/internal-parse-maybe-assign/fixture/add-array.gs create mode 100644 packages/internal-parse-maybe-assign/fixture/assign-from.gs create mode 100644 packages/internal-parse-maybe-assign/index.js create mode 100644 packages/internal-parse-maybe-assign/index.spec.js create mode 100644 packages/keyword-add-array/fixture/assign-from.gs create mode 100644 packages/keyword-add-array/fixture/assign-from.js create mode 100644 packages/keyword-add-array/fixture/const.gs create mode 100644 packages/keyword-assign-from/fixture/add-array.gs create mode 100644 packages/keyword-assign-from/fixture/assign-from.gs create mode 100644 packages/keyword-assign-from/fixture/assign-from.js create mode 100644 packages/keyword-assign-from/fixture/const.gs create mode 100644 packages/keyword-assign-from/index.js create mode 100644 packages/keyword-assign-from/index.spec.js diff --git a/README.md b/README.md index fa52e07..e01d35d 100644 --- a/README.md +++ b/README.md @@ -466,6 +466,18 @@ const a = { }; ``` +### Assign from + +```gs +const a = from 'a'; +``` + +The same as: + +```js +const a = require('a'); +``` + ## How to contribute? Clone the registry, create a new keyword with a prefix `keyword-`, then create directory `fixture` and put there two files with extensions `.js` and `.gs`. Half way done 🥳! diff --git a/packages/goldstein/index.spec.js b/packages/goldstein/index.spec.js index 1f9f429..809567f 100644 --- a/packages/goldstein/index.spec.js +++ b/packages/goldstein/index.spec.js @@ -1,5 +1,6 @@ import {test} from 'supertape'; import montag from 'montag'; +import tryCatch from 'try-catch'; import { compile, keywords, @@ -283,6 +284,19 @@ test('goldstein: parse: import', (t) => { t.end(); }); +test('goldstein: parse: assign from', (t) => { + const result = compile(montag` + const a = from 'x'; + `); + + const expected = montag` + const a = require('x');\n + `; + + t.equal(result, expected); + t.end(); +}); + test('goldstein: parse: broken string', (t) => { const result = compile(montag` const a = 'hello; @@ -482,3 +496,19 @@ test('goldstein: compile: new line before if', (t) => { t.equal(result, expected); t.end(); }); + +test('goldstein: compile: parse-maybe-array', (t) => { + const source = montag` + a += [1]; + `; + + const [error, result] = tryCatch(compile, source, { + keywords: {}, + }); + + console.log(error, result); + + t.equal(error.message, `☝️Looks like 'keyword-add-array' is missing.`); + t.end(); +}); + diff --git a/packages/goldstein/parser.js b/packages/goldstein/parser.js index 032682a..9bf552f 100644 --- a/packages/goldstein/parser.js +++ b/packages/goldstein/parser.js @@ -17,6 +17,8 @@ import keywordBrokenString from '../keyword-broken-string/index.js'; import keywordMissingInitializer from '../keyword-missing-initializer/index.js'; import keywordUselessComma from '../keyword-useless-comma/index.js'; import keywordUselessSemicolon from '../keyword-useless-semicolon/index.js'; +import keywordAssignFrom from '../keyword-assign-from/index.js'; +import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; const {values} = Object; @@ -37,8 +39,13 @@ const defaultKeywords = { keywordMissingInitializer, keywordUselessComma, keywordUselessSemicolon, + keywordAssignFrom, }; +const internals = [ + internalParseMaybeAssign, +]; + export const keywords = defaultKeywords; export const parse = (source, options = {}) => { @@ -47,6 +54,7 @@ export const parse = (source, options = {}) => { const {parse} = extendParser([ typescript(), + ...internals, ...extensions, ]); diff --git a/packages/internal-parse-maybe-assign/fixture/add-array.gs b/packages/internal-parse-maybe-assign/fixture/add-array.gs new file mode 100644 index 0000000..b833197 --- /dev/null +++ b/packages/internal-parse-maybe-assign/fixture/add-array.gs @@ -0,0 +1 @@ +a += [b]; \ No newline at end of file diff --git a/packages/internal-parse-maybe-assign/fixture/assign-from.gs b/packages/internal-parse-maybe-assign/fixture/assign-from.gs new file mode 100644 index 0000000..b398e8c --- /dev/null +++ b/packages/internal-parse-maybe-assign/fixture/assign-from.gs @@ -0,0 +1 @@ +const a = from 'x'; \ No newline at end of file diff --git a/packages/internal-parse-maybe-assign/index.js b/packages/internal-parse-maybe-assign/index.js new file mode 100644 index 0000000..119b597 --- /dev/null +++ b/packages/internal-parse-maybe-assign/index.js @@ -0,0 +1,114 @@ +import {tokTypes as tt} from 'acorn'; +import {types} from 'putout'; +import {DestructuringErrors} from '../operator/index.js'; + +const {isArrayExpression} = types; + +export default function internalParseMaybeAssign(Parser) { + return class extends Parser { + parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { + return parseMaybeAssign.call(this, forInit, refDestructuringErrors, afterLeftParse); + } + + goldsteinParseFrom() { + throw Error(`☝️Looks like 'keyword-assign-from' is missing.`); + } + + goldsteinCreateAddArray() { + throw Error(`☝️Looks like 'keyword-add-array' is missing.`); + } + }; +} + +export function parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { + /* c8 ignore start */ + if (this.isContextual('yield')) { + if (this.inGenerator) + return this.parseYield(forInit); + + // The tokenizer will assume an expression is allowed after + // `yield`, but this isn't that kind of yield + this.exprAllowed = false; /* c8 ignore end */ + } + + let ownDestructuringErrors = false; + let oldParenAssign = -1; + let oldTrailingComma = -1; + let oldDoubleProto = -1; + + if (refDestructuringErrors) { + oldParenAssign = refDestructuringErrors.parenthesizedAssign; + oldTrailingComma = refDestructuringErrors.trailingComma; + oldDoubleProto = refDestructuringErrors.doubleProto; + refDestructuringErrors.parenthesizedAssign = -1; + refDestructuringErrors.trailingComma = -1; + } else { + refDestructuringErrors = new DestructuringErrors(); + ownDestructuringErrors = true; + } + + const startPos = this.start; + const {startLoc} = this; + + if (this.type === tt.parenL || this.type === tt.name) { + this.potentialArrowAt = this.start; + this.potentialArrowInForAwait = forInit === 'await'; + } + + let left = this.parseMaybeConditional(forInit, refDestructuringErrors); + + if (afterLeftParse) + left = afterLeftParse.call(this, left, startPos, startLoc); + + /* c8 ignore start */ + if (this.type.isAssign) { + const node = this.startNodeAt(startPos, startLoc); + + node.operator = this.value; + + if (this.type === tt.eq) + left = this.toAssignable(left, false, refDestructuringErrors); + + if (!ownDestructuringErrors) { + refDestructuringErrors.parenthesizedAssign = -1; + refDestructuringErrors.trailingComma = -1; + refDestructuringErrors.doubleProto = -1; + } + + if (refDestructuringErrors.shorthandAssign >= left.start) + refDestructuringErrors.shorthandAssign = -1; + + // reset because shorthand default was used correctly + if (this.type === tt.eq) + this.checkLValPattern(left); + else + this.checkLValSimple(left); + + node.left = left; + this.next(); + node.right = this.parseMaybeAssign(forInit); + + if (oldDoubleProto > -1) + refDestructuringErrors.doubleProto = oldDoubleProto; + + if (node.operator === '+=' && isArrayExpression(node.right)) + return this.goldsteinCreateAddArray(node); + + return this.finishNode(node, 'AssignmentExpression'); + } + + /* c8 ignore end */ + if (ownDestructuringErrors) + this.checkExpressionErrors(refDestructuringErrors, true); + + if (oldParenAssign > -1) + refDestructuringErrors.parenthesizedAssign = oldParenAssign; + + if (oldTrailingComma > -1) + refDestructuringErrors.trailingComma = oldTrailingComma; + + if (left.name === 'from') + return this.goldsteinParseFrom(left); + + return left; +} diff --git a/packages/internal-parse-maybe-assign/index.spec.js b/packages/internal-parse-maybe-assign/index.spec.js new file mode 100644 index 0000000..f988286 --- /dev/null +++ b/packages/internal-parse-maybe-assign/index.spec.js @@ -0,0 +1,20 @@ +import {createTest} from '../test/index.js'; +import keywordFn from './index.js'; +import ts from 'acorn-typescript'; +import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; + +const test = createTest( + import.meta.url, + ts(), + internalParseMaybeAssign, +); + +test('goldstein: keyword: assign-from: raise: add-array', (t) => { + t.raise('add-array', `☝️Looks like 'keyword-add-array' is missing.`); + t.end(); +}); + +test('goldstein: keyword: assign-from: raise: assign-from', (t) => { + t.raise('assign-from', `☝️Looks like 'keyword-assign-from' is missing.`); + t.end(); +}); diff --git a/packages/keyword-add-array/fixture/assign-from.gs b/packages/keyword-add-array/fixture/assign-from.gs new file mode 100644 index 0000000..b398e8c --- /dev/null +++ b/packages/keyword-add-array/fixture/assign-from.gs @@ -0,0 +1 @@ +const a = from 'x'; \ No newline at end of file diff --git a/packages/keyword-add-array/fixture/assign-from.js b/packages/keyword-add-array/fixture/assign-from.js new file mode 100644 index 0000000..1319b1b --- /dev/null +++ b/packages/keyword-add-array/fixture/assign-from.js @@ -0,0 +1 @@ +const a = require('x'); diff --git a/packages/keyword-add-array/fixture/const.gs b/packages/keyword-add-array/fixture/const.gs new file mode 100644 index 0000000..2cf49f3 --- /dev/null +++ b/packages/keyword-add-array/fixture/const.gs @@ -0,0 +1,5 @@ +const a = 'x'; +const b = from; +const c = from(); +const d = from(1, 2); +const e = from * x; diff --git a/packages/keyword-add-array/index.js b/packages/keyword-add-array/index.js index 7a774c8..51dec86 100644 --- a/packages/keyword-add-array/index.js +++ b/packages/keyword-add-array/index.js @@ -1,12 +1,10 @@ import {types} from 'putout'; -import {tokTypes as tt} from 'acorn'; -import {DestructuringErrors} from '../operator/index.js'; +import {parseMaybeAssign} from '../internal-parse-maybe-assign/index.js'; const {assign} = Object; const { identifier, - isArrayExpression, memberExpression, spreadElement, } = types; @@ -14,103 +12,18 @@ const { export default function keywordAddArray(Parser) { return class extends Parser { parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { - /* c8 ignore start */ - if (this.isContextual('yield')) { - if (this.inGenerator) - return this.parseYield(forInit); - - // The tokenizer will assume an expression is allowed after - // `yield`, but this isn't that kind of yield - this.exprAllowed = false; - } - - /* c8 ignore end */ - let ownDestructuringErrors = false; - let oldParenAssign = -1; - let oldTrailingComma = -1; - let oldDoubleProto = -1; - - if (refDestructuringErrors) { - oldParenAssign = refDestructuringErrors.parenthesizedAssign; - oldTrailingComma = refDestructuringErrors.trailingComma; - oldDoubleProto = refDestructuringErrors.doubleProto; - refDestructuringErrors.parenthesizedAssign = -1; - refDestructuringErrors.trailingComma = -1; - } else { - refDestructuringErrors = new DestructuringErrors(); - ownDestructuringErrors = true; - } - - const startPos = this.start; - const {startLoc} = this; - - if (this.type === tt.parenL || this.type === tt.name) { - this.potentialArrowAt = this.start; - this.potentialArrowInForAwait = forInit === 'await'; - } - - let left = this.parseMaybeConditional(forInit, refDestructuringErrors); - - if (afterLeftParse) - left = afterLeftParse.call(this, left, startPos, startLoc); - - if (this.type.isAssign) { - const node = this.startNodeAt(startPos, startLoc); - - node.operator = this.value; - - if (this.type === tt.eq) - left = this.toAssignable(left, false, refDestructuringErrors); - - if (!ownDestructuringErrors) { - refDestructuringErrors.parenthesizedAssign = -1; - refDestructuringErrors.trailingComma = -1; - refDestructuringErrors.doubleProto = -1; - } - - if (refDestructuringErrors.shorthandAssign >= left.start) - refDestructuringErrors.shorthandAssign = -1; - - // reset because shorthand default was used correctly - if (this.type === tt.eq) - this.checkLValPattern(left); - else - this.checkLValSimple(left); - - node.left = left; - this.next(); - node.right = this.parseMaybeAssign(forInit); - - if (oldDoubleProto > -1) - refDestructuringErrors.doubleProto = oldDoubleProto; - - if (node.operator === '+=' && isArrayExpression(node.right)) - return createAppendNode(this, node); - - return this.finishNode(node, 'AssignmentExpression'); - } - - if (ownDestructuringErrors) - this.checkExpressionErrors(refDestructuringErrors, true); - - if (oldParenAssign > -1) - refDestructuringErrors.parenthesizedAssign = oldParenAssign; + return parseMaybeAssign.call(this, forInit, refDestructuringErrors, afterLeftParse); + } + + goldsteinCreateAddArray(node) { + const {left, right} = node; - if (oldTrailingComma > -1) - refDestructuringErrors.trailingComma = oldTrailingComma; + assign(node, { + callee: memberExpression(left, identifier('push')), + arguments: [spreadElement(right)], + }); - return left; + return this.finishNode(node, 'CallExpression'); } }; } - -function createAppendNode(context, node) { - const {left, right} = node; - - assign(node, { - callee: memberExpression(left, identifier('push')), - arguments: [spreadElement(right)], - }); - - return context.finishNode(node, 'CallExpression'); -} diff --git a/packages/keyword-assign-from/fixture/add-array.gs b/packages/keyword-assign-from/fixture/add-array.gs new file mode 100644 index 0000000..b833197 --- /dev/null +++ b/packages/keyword-assign-from/fixture/add-array.gs @@ -0,0 +1 @@ +a += [b]; \ No newline at end of file diff --git a/packages/keyword-assign-from/fixture/assign-from.gs b/packages/keyword-assign-from/fixture/assign-from.gs new file mode 100644 index 0000000..b398e8c --- /dev/null +++ b/packages/keyword-assign-from/fixture/assign-from.gs @@ -0,0 +1 @@ +const a = from 'x'; \ No newline at end of file diff --git a/packages/keyword-assign-from/fixture/assign-from.js b/packages/keyword-assign-from/fixture/assign-from.js new file mode 100644 index 0000000..1319b1b --- /dev/null +++ b/packages/keyword-assign-from/fixture/assign-from.js @@ -0,0 +1 @@ +const a = require('x'); diff --git a/packages/keyword-assign-from/fixture/const.gs b/packages/keyword-assign-from/fixture/const.gs new file mode 100644 index 0000000..2cf49f3 --- /dev/null +++ b/packages/keyword-assign-from/fixture/const.gs @@ -0,0 +1,5 @@ +const a = 'x'; +const b = from; +const c = from(); +const d = from(1, 2); +const e = from * x; diff --git a/packages/keyword-assign-from/index.js b/packages/keyword-assign-from/index.js new file mode 100644 index 0000000..b17d669 --- /dev/null +++ b/packages/keyword-assign-from/index.js @@ -0,0 +1,26 @@ +import {tokTypes as tt} from '../operator/index.js'; +import {parseMaybeAssign} from '../internal-parse-maybe-assign/index.js'; + +export default function fn(Parser) { + return class extends Parser { + parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { + return parseMaybeAssign.call(this, forInit, refDestructuringErrors, afterLeftParse); + } + + goldsteinParseFrom(node) { + if (this.type === tt.semi) + return node; + + const arg = this.parseExprAtom(); + + return { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'require', + }, + arguments: [arg], + }; + } + }; +} diff --git a/packages/keyword-assign-from/index.spec.js b/packages/keyword-assign-from/index.spec.js new file mode 100644 index 0000000..9ca2bfe --- /dev/null +++ b/packages/keyword-assign-from/index.spec.js @@ -0,0 +1,26 @@ +import {createTest} from '../test/index.js'; +import keywordFn from './index.js'; +import ts from 'acorn-typescript'; +import internalParseMaybeAssign from '../internal-parse-maybe-assign/index.js'; + +const test = createTest( + import.meta.url, + ts(), + internalParseMaybeAssign, + keywordFn, +); + +test('goldstein: keyword: assign-from', (t) => { + t.compile('assign-from'); + t.end(); +}); + +test('goldstein: internal: parse-maybe-assign: const: no compile', (t) => { + t.noCompile('const'); + t.end(); +}); + +test('goldstein: internal: parse-maybe-assign: raise: add-array', (t) => { + t.raise('add-array', `☝️Looks like 'keyword-add-array' is missing.`); + t.end(); +});