From 924f1827539ab4159fda981b6a9509cc0653e91d Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 19 Oct 2023 20:19:21 +0300 Subject: [PATCH] feature: goldstein: add-array: add --- .github/workflows/nodejs-pr.yml | 1 + .github/workflows/nodejs.yml | 1 + README.md | 16 +++ packages/goldstein/index.spec.js | 15 +++ packages/goldstein/parser.js | 2 + .../keyword-add-array/fixture/add-array.gs | 1 + .../keyword-add-array/fixture/add-array.js | 1 + packages/keyword-add-array/index.js | 121 ++++++++++++++++++ packages/keyword-add-array/index.spec.js | 9 ++ packages/operator/index.js | 7 + 10 files changed, 174 insertions(+) create mode 100644 packages/keyword-add-array/fixture/add-array.gs create mode 100644 packages/keyword-add-array/fixture/add-array.js create mode 100644 packages/keyword-add-array/index.js create mode 100644 packages/keyword-add-array/index.spec.js diff --git a/.github/workflows/nodejs-pr.yml b/.github/workflows/nodejs-pr.yml index 7d21a55..e687b72 100644 --- a/.github/workflows/nodejs-pr.yml +++ b/.github/workflows/nodejs-pr.yml @@ -10,6 +10,7 @@ jobs: - 16.x - 18.x - 20.x + - 21.x steps: - uses: actions/checkout@v3 - uses: oven-sh/setup-bun@v1 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d05377c..6eacb1b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,6 +12,7 @@ jobs: - 16.x - 18.x - 20.x + - 21.x steps: - uses: actions/checkout@v3 with: diff --git a/README.md b/README.md index 60ce9c3..f837348 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,22 @@ function hello() { } ``` + +### `append array` + +```swift +const a = [1]; + +a += [2, 3]; +``` + +Is the same as: + +```js +const a = [1]; +a.push(...[2, 3]); +``` + ### `guard` Applies not to `IfCondition`: diff --git a/packages/goldstein/index.spec.js b/packages/goldstein/index.spec.js index ad07522..58a9bd8 100644 --- a/packages/goldstein/index.spec.js +++ b/packages/goldstein/index.spec.js @@ -234,6 +234,21 @@ test('goldstein: parse: if', (t) => { t.end(); }); +test('goldstein: parse: append array', (t) => { + const result = compile(montag` + const a = [1]; + a += [2, 3]; + `); + + const expected = montag` + const a = [1]; + a.push(...[2, 3]);\n + `; + + t.equal(result, expected); + t.end(); +}); + test('goldstein: parse: import', (t) => { const result = compile(montag` import hello from './hello.gs'; diff --git a/packages/goldstein/parser.js b/packages/goldstein/parser.js index 8711cda..38b53ba 100644 --- a/packages/goldstein/parser.js +++ b/packages/goldstein/parser.js @@ -11,6 +11,7 @@ import keywordFreeze from '../keyword-freeze/index.js'; import keywordIf from '../keyword-if/index.js'; import keywordImport from '../keyword-import/index.js'; import keywordArrow from '../keyword-arrow/index.js'; +import keywordAddArray from '../keyword-add-array/index.js'; const defaultKeywords = { keywordFn, @@ -23,6 +24,7 @@ const defaultKeywords = { keywordIf, keywordImport, keywordArrow, + keywordAddArray, stringInterpolation, }; diff --git a/packages/keyword-add-array/fixture/add-array.gs b/packages/keyword-add-array/fixture/add-array.gs new file mode 100644 index 0000000..6939b5b --- /dev/null +++ b/packages/keyword-add-array/fixture/add-array.gs @@ -0,0 +1 @@ +a += [1, 2, 3]; \ No newline at end of file diff --git a/packages/keyword-add-array/fixture/add-array.js b/packages/keyword-add-array/fixture/add-array.js new file mode 100644 index 0000000..7867632 --- /dev/null +++ b/packages/keyword-add-array/fixture/add-array.js @@ -0,0 +1 @@ +a.push(...[1, 2, 3]); diff --git a/packages/keyword-add-array/index.js b/packages/keyword-add-array/index.js new file mode 100644 index 0000000..bafdf9c --- /dev/null +++ b/packages/keyword-add-array/index.js @@ -0,0 +1,121 @@ +import {types} from 'putout'; +import {tokTypes as tt} from 'acorn'; +import {DestructuringErrors} from '../operator/index.js'; + +const {assign} = Object; + +const { + identifier, + isArrayExpression, + memberExpression, + spreadElement, +} = types; + +export default function keywordAddArray(Parser) { + return class extends Parser { + parseMaybeAssign(forInit, refDestructuringErrors, afterLeftParse) { + 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; + } + + 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 = 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 = refDestructuringErrors.trailingComma = 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; + } + + if (oldTrailingComma > -1) { + refDestructuringErrors.trailingComma = oldTrailingComma; + } + + return left; + } + }; +} + +function createAppendNode(context, node) { + const {left, right} = node; + + assign(node, { + a: 'x', + callee: memberExpression(left, identifier('push')), + arguments: [spreadElement(right)], + }); + + return context.finishNode(node, 'CallExpression'); +} diff --git a/packages/keyword-add-array/index.spec.js b/packages/keyword-add-array/index.spec.js new file mode 100644 index 0000000..dd55002 --- /dev/null +++ b/packages/keyword-add-array/index.spec.js @@ -0,0 +1,9 @@ +import {createTest} from '../test/index.js'; +import keywordFn from './index.js'; + +const test = createTest(import.meta.url, keywordFn); + +test('goldstein: keyword: add-array', (t) => { + t.compile('add-array'); + t.end(); +}); diff --git a/packages/operator/index.js b/packages/operator/index.js index ecb54af..c171f04 100644 --- a/packages/operator/index.js +++ b/packages/operator/index.js @@ -9,3 +9,10 @@ export function addKeyword(keyword, keywords) { return RegExp(str); } +export class DestructuringErrors { + shorthandAssign = -1; + trailingComma = -1; + parenthesizedAssign = -1; + parenthesizedBind = -1; + doubleProto = -1; +}