From 70ee2c810886bc8373ebffa771d3ad9a29345843 Mon Sep 17 00:00:00 2001 From: "William C. Johnson" Date: Sun, 16 Jul 2017 20:46:52 -0400 Subject: [PATCH] Syntactic placeholders commit d5d4e7491de9a8b4e99128197534ca411aee2289 Author: William C. Johnson Date: Sun Jul 16 20:41:48 2017 -0400 Allow placeholder to be changed via config commit 32e43a0f51ef1e62c48a6b52cd5af0843ab4b4c7 Author: William C. Johnson Date: Sun Jul 16 20:24:59 2017 -0400 Spread placeholder tests commit 73b9d329505a49a4024997299f90dc20190d65cc Author: William C. Johnson Date: Sun Jul 16 15:31:50 2017 -0400 Initial implementation of syntactic placeholders --- src/options.js | 5 +- src/parser/expression.js | 19 ++- src/plugins/syntacticPlaceholder.js | 51 ++++++++ src/plugins/tildeCall.js | 2 +- src/registerPlugins.js | 5 + .../basic/basic/actual.js | 1 + .../basic/basic/expected.json | 64 ++++++++++ .../basic/indexed/actual.js | 1 + .../basic/indexed/expected.json | 65 ++++++++++ .../basic/spread-indexed/actual.js | 1 + .../basic/spread-indexed/expected.json | 118 ++++++++++++++++++ .../basic/spread/actual.js | 1 + .../basic/spread/expected.json | 117 +++++++++++++++++ .../syntactic-placeholder/options.json | 8 ++ .../opts/change-placeholder/actual.js | 1 + .../expected.changePlaceholder.json | 64 ++++++++++ .../opts/change-placeholder/expected.json | 66 ++++++++++ .../opts/change-placeholder/options.json | 12 ++ test/utils/TestRunner.js | 4 + 19 files changed, 599 insertions(+), 6 deletions(-) create mode 100644 src/plugins/syntacticPlaceholder.js create mode 100644 test/fixtures/syntactic-placeholder/basic/basic/actual.js create mode 100644 test/fixtures/syntactic-placeholder/basic/basic/expected.json create mode 100644 test/fixtures/syntactic-placeholder/basic/indexed/actual.js create mode 100644 test/fixtures/syntactic-placeholder/basic/indexed/expected.json create mode 100644 test/fixtures/syntactic-placeholder/basic/spread-indexed/actual.js create mode 100644 test/fixtures/syntactic-placeholder/basic/spread-indexed/expected.json create mode 100644 test/fixtures/syntactic-placeholder/basic/spread/actual.js create mode 100644 test/fixtures/syntactic-placeholder/basic/spread/expected.json create mode 100644 test/fixtures/syntactic-placeholder/options.json create mode 100644 test/fixtures/syntactic-placeholder/opts/change-placeholder/actual.js create mode 100644 test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.changePlaceholder.json create mode 100644 test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.json create mode 100644 test/fixtures/syntactic-placeholder/opts/change-placeholder/options.json diff --git a/src/options.js b/src/options.js index 72c65f34a6..513069be70 100755 --- a/src/options.js +++ b/src/options.js @@ -9,7 +9,8 @@ export const defaultOptions: { allowImportExportEverywhere: boolean, allowSuperOutsideMethod: boolean, plugins: Array, - strictMode: any + strictMode: any, + placeholder: string } = { // Source type ("script" or "module") for different semantics sourceType: "script", @@ -30,6 +31,8 @@ export const defaultOptions: { plugins: [], // TODO strictMode: null, + // Default placeholder for use with syntacticPlaceholder plugin + placeholder: "_" }; // Interpret and default an options object diff --git a/src/parser/expression.js b/src/parser/expression.js index 4a05f1c992..1211ba5d0a 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -412,7 +412,7 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { node.property = this.parseExprAtom(); node.computed = true; } else { - node.property = this.parseIdentifier(true); + node.property = this.parseIdentifierOrPlaceholder(true); node.computed = false; } base = this.finishNode(node, "MemberExpression"); @@ -431,7 +431,7 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { node.property = this.parseLiteral(this.state.value, "NumericLiteral"); node.computed = true; } else { - node.property = this.parseIdentifier(true); + node.property = this.parseIdentifierOrPlaceholder(true); node.computed = false; } } else if (op === "?[") { @@ -651,7 +651,7 @@ pp.parseExprAtom = function (refShorthandDefaultPos) { node = this.startNode(); const allowAwait = this.state.value === "await" && this.state.inAsync; const allowYield = this.shouldAllowYieldIdentifier(); - const id = this.parseIdentifier(allowAwait || allowYield); + const id = this.parseIdentifierOrPlaceholder(allowAwait || allowYield); if (id.name === "await") { if (this.state.inAsync || this.inModule) { @@ -661,7 +661,7 @@ pp.parseExprAtom = function (refShorthandDefaultPos) { this.next(); return this.parseFunction(node, false, false, true); } else if (canBeArrow && id.name === "async" && this.match(tt.name)) { - const params = [this.parseIdentifier()]; + const params = [this.parseIdentifierOrPlaceholder()]; this.check(tt.arrow); // let foo = bar => {}; return this.parseArrowExpression(node, params, true); @@ -1134,6 +1134,7 @@ pp.parseObj = function (isPattern, refShorthandDefaultPos) { if (!isPattern && this.isContextual("async")) { if (isGenerator) this.unexpected(); + // TODO: syntacticPlaceholder: is a placeholder legal here? const asyncId = this.parseIdentifier(); if (this.match(tt.colon) || this.match(tt.parenL) || this.match(tt.braceR) || this.match(tt.eq) || this.match(tt.comma) || (this.hasPlugin("lightscript") && this.isLineBreak())) { prop.key = asyncId; @@ -1262,6 +1263,7 @@ pp.parsePropertyName = function (prop) { prop.computed = false; const oldInPropertyName = this.state.inPropertyName; this.state.inPropertyName = true; + // TODO: syntacticPlaceholder: is a placeholder legal here? prop.key = (this.match(tt.num) || this.match(tt.string)) ? this.parseExprAtom() : this.parseIdentifier(true); this.state.inPropertyName = oldInPropertyName; } @@ -1465,6 +1467,15 @@ pp.parseIdentifier = function (liberal) { return this.finishNode(node, "Identifier"); }; +// Syntactic placeholders: shunt based on plugin status +pp.parseIdentifierOrPlaceholder = function(liberal) { + if (this.hasPlugin("syntacticPlaceholder")) { + return this._parseIdentifierOrPlaceholder(liberal); + } else { + return this.parseIdentifier(liberal); + } +}; + pp.checkReservedWord = function (word, startLoc, checkKeywords, isBinding) { if (this.isReservedWord(word) || (checkKeywords && this.isKeyword(word))) { this.raise(startLoc, word + " is a reserved word"); diff --git a/src/plugins/syntacticPlaceholder.js b/src/plugins/syntacticPlaceholder.js new file mode 100644 index 0000000000..e54b5a0103 --- /dev/null +++ b/src/plugins/syntacticPlaceholder.js @@ -0,0 +1,51 @@ +import Parser from "../parser"; +import { types as tt } from "../tokenizer/types"; +const pp = Parser.prototype; + +export default function(parser) { + if (parser.__syntacticPlaceholderPluginInstalled) return; + parser.__syntacticPlaceholderPluginInstalled = true; + + const ph = parser.options.placeholder || "_"; + const quotedPh = (ph + "").replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); + const phRegex = new RegExp("^" + quotedPh + "([0-9]*)$"); + + pp.isPlaceholderName = function(name) { + return phRegex.test(name); + }; + + // c/p parseIdentifier + pp._parseIdentifierOrPlaceholder = function(liberal) { + const node = this.startNode(); + if (!liberal) { + this.checkReservedWord(this.state.value, this.state.start, !!this.state.type.keyword, false); + } + + let name; + if (this.match(tt.name)) { + name = this.state.value; + } else if (this.state.type.keyword) { + name = this.state.type.keyword; + } else { + this.unexpected(); + } + + const matches = phRegex.exec(name); + if (matches) { + if (matches[1]) node.index = parseInt(matches[1]); + this.next(); + return this.finishNode(node, "PlaceholderExpression"); + } + + node.name = name; + + if (!liberal && node.name === "await" && this.state.inAsync) { + this.raise(node.start, "invalid use of await inside of an async function"); + } + + node.loc.identifierName = node.name; + + this.next(); + return this.finishNode(node, "Identifier"); + }; +} diff --git a/src/plugins/tildeCall.js b/src/plugins/tildeCall.js index 55fd3db574..6274e75f48 100644 --- a/src/plugins/tildeCall.js +++ b/src/plugins/tildeCall.js @@ -13,7 +13,7 @@ export default function(parser) { node.left = left; // allow `this`, Identifier or MemberExpression, but not calls - const right = this.match(tt._this) ? this.parseExprAtom() : this.parseIdentifier(); + const right = this.match(tt._this) ? this.parseExprAtom() : this.parseIdentifierOrPlaceholder(); node.right = this.parseSubscripts(right, this.state.start, this.state.startLoc, true); // Allow safe tilde calls (a~b?(c)) diff --git a/src/registerPlugins.js b/src/registerPlugins.js index 8ca0b97c39..7461a802ff 100644 --- a/src/registerPlugins.js +++ b/src/registerPlugins.js @@ -7,6 +7,7 @@ import safeCallExistentialPlugin from "./plugins/safeCallExistential"; import bangCallPlugin from "./plugins/bangCall"; import significantWhitespacePlugin from "./plugins/significantWhitespace"; import enhancedComprehensionPlugin from "./plugins/enhancedComprehension"; +import syntacticPlaceholderPlugin from "./plugins/syntacticPlaceholder"; import { matchCoreSyntax, match } from "./plugins/match"; function noncePlugin() {} @@ -79,4 +80,8 @@ export default function registerPlugins(plugins, metadata) { registerPlugin("objectBlockAmbiguity_preferObject", noncePlugin, { dependencies: ["lightscript"] }); + + // Parse identifiers beginning with `_` or another user-chosen symbol + // as PlaceholderExpressions. + registerPlugin("syntacticPlaceholder", syntacticPlaceholderPlugin); } diff --git a/test/fixtures/syntactic-placeholder/basic/basic/actual.js b/test/fixtures/syntactic-placeholder/basic/basic/actual.js new file mode 100644 index 0000000000..31354ec138 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/basic/actual.js @@ -0,0 +1 @@ +_ diff --git a/test/fixtures/syntactic-placeholder/basic/basic/expected.json b/test/fixtures/syntactic-placeholder/basic/basic/expected.json new file mode 100644 index 0000000000..dd674cdc43 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/basic/expected.json @@ -0,0 +1,64 @@ +{ + "type": "File", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "expression": { + "type": "PlaceholderExpression", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/syntactic-placeholder/basic/indexed/actual.js b/test/fixtures/syntactic-placeholder/basic/indexed/actual.js new file mode 100644 index 0000000000..77d26c76e0 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/indexed/actual.js @@ -0,0 +1 @@ +_1 diff --git a/test/fixtures/syntactic-placeholder/basic/indexed/expected.json b/test/fixtures/syntactic-placeholder/basic/indexed/expected.json new file mode 100644 index 0000000000..c0ab205a92 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/indexed/expected.json @@ -0,0 +1,65 @@ +{ + "type": "File", + "start": 0, + "end": 2, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 2 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 2, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 2 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 2, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 2 + } + }, + "expression": { + "type": "PlaceholderExpression", + "start": 0, + "end": 2, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 2 + } + }, + "index": 1 + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/syntactic-placeholder/basic/spread-indexed/actual.js b/test/fixtures/syntactic-placeholder/basic/spread-indexed/actual.js new file mode 100644 index 0000000000..374c6a6ea2 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/spread-indexed/actual.js @@ -0,0 +1 @@ +-> [..._1] diff --git a/test/fixtures/syntactic-placeholder/basic/spread-indexed/expected.json b/test/fixtures/syntactic-placeholder/basic/spread-indexed/expected.json new file mode 100644 index 0000000000..9896c70ccc --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/spread-indexed/expected.json @@ -0,0 +1,118 @@ +{ + "type": "File", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "id": null, + "generator": false, + "expression": true, + "async": false, + "params": [], + "skinny": true, + "body": { + "type": "ArrayExpression", + "start": 3, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "elements": [ + { + "type": "SpreadElement", + "start": 4, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "argument": { + "type": "PlaceholderExpression", + "start": 7, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "index": 1 + } + } + ] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/syntactic-placeholder/basic/spread/actual.js b/test/fixtures/syntactic-placeholder/basic/spread/actual.js new file mode 100644 index 0000000000..43e03d1cd0 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/spread/actual.js @@ -0,0 +1 @@ +-> [..._] diff --git a/test/fixtures/syntactic-placeholder/basic/spread/expected.json b/test/fixtures/syntactic-placeholder/basic/spread/expected.json new file mode 100644 index 0000000000..758c275d60 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/basic/spread/expected.json @@ -0,0 +1,117 @@ +{ + "type": "File", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "id": null, + "generator": false, + "expression": true, + "async": false, + "params": [], + "skinny": true, + "body": { + "type": "ArrayExpression", + "start": 3, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "elements": [ + { + "type": "SpreadElement", + "start": 4, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "argument": { + "type": "PlaceholderExpression", + "start": 7, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 8 + } + } + } + } + ] + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/syntactic-placeholder/options.json b/test/fixtures/syntactic-placeholder/options.json new file mode 100644 index 0000000000..60bbeba2ff --- /dev/null +++ b/test/fixtures/syntactic-placeholder/options.json @@ -0,0 +1,8 @@ +{ + "alternatives": { + "all": { + "allPlugins": true, + "excludePlugins": ["estree"] + } + } +} diff --git a/test/fixtures/syntactic-placeholder/opts/change-placeholder/actual.js b/test/fixtures/syntactic-placeholder/opts/change-placeholder/actual.js new file mode 100644 index 0000000000..52c184f186 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/opts/change-placeholder/actual.js @@ -0,0 +1 @@ +P diff --git a/test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.changePlaceholder.json b/test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.changePlaceholder.json new file mode 100644 index 0000000000..dd674cdc43 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.changePlaceholder.json @@ -0,0 +1,64 @@ +{ + "type": "File", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "expression": { + "type": "PlaceholderExpression", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.json b/test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.json new file mode 100644 index 0000000000..f254aba2d4 --- /dev/null +++ b/test/fixtures/syntactic-placeholder/opts/change-placeholder/expected.json @@ -0,0 +1,66 @@ +{ + "type": "File", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "expression": { + "type": "Identifier", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + }, + "identifierName": "P" + }, + "name": "P" + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/syntactic-placeholder/opts/change-placeholder/options.json b/test/fixtures/syntactic-placeholder/opts/change-placeholder/options.json new file mode 100644 index 0000000000..4674015b3c --- /dev/null +++ b/test/fixtures/syntactic-placeholder/opts/change-placeholder/options.json @@ -0,0 +1,12 @@ +{ + "alternatives": { + "changePlaceholder": { + "allPlugins": true, + "excludePlugins": ["estree"], + "expected": "expected.changePlaceholder.json", + "parserOpts": { + "placeholder": "P" + } + } + } +} diff --git a/test/utils/TestRunner.js b/test/utils/TestRunner.js index 55acd4e0dd..18a90bfa85 100644 --- a/test/utils/TestRunner.js +++ b/test/utils/TestRunner.js @@ -235,6 +235,10 @@ exports.Test = class Test { } } } + + if (alt.parserOpts) { + this.parserOpts = Object.assign({}, this.parserOpts, alt.parserOpts); + } } includePlugins(plugins, list) {