diff --git a/AUTHORS b/AUTHORS index c7a0fb8a0..6195b1ae8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -30,6 +30,7 @@ Fabien LOISON Felix Maier Forbes Lindesay Gilad Peleg +Huáng Jùnliàng impinball Ingvar Stepanyan Jackson Ray Hamilton @@ -68,6 +69,7 @@ Olivier Thomann Oskar Schöldström Paul Harper Peter Rust +piotr PlNG Praveen N Prayag Verma @@ -80,10 +82,14 @@ Sebastian McKenzie Shahar Soel Sheel Bedi Simen Bekkhus +susiwen +susiwen8 Teddy Katz Timothy Gu Toru Nagashima +tuesmiddt Victor Homyakov +Vladislav Tupikin Wexpo Lyu zsjforcn 龙腾道 diff --git a/acorn-loose/README.md b/acorn-loose/README.md index 3d8f5bac5..4641bd4fd 100644 --- a/acorn-loose/README.md +++ b/acorn-loose/README.md @@ -60,3 +60,7 @@ take the **`LooseParser`** class exported by the module, and call its static `extend` method with one or more plugins to get a customized parser class. The class has a static `parse` method that acts like the top-level `parse` method. + +**isDummy**`(node)` takes a `Node` and returns `true` if it is a dummy node +inserted by the parser. The function performs a simple equality check on the +node's name. diff --git a/acorn-loose/src/index.js b/acorn-loose/src/index.js index 903781c5f..cfa0b497b 100644 --- a/acorn-loose/src/index.js +++ b/acorn-loose/src/index.js @@ -36,6 +36,7 @@ import "./statement" import "./expression" export {LooseParser} from "./state" +export {isDummy} from "./parseutil" defaultOptions.tabSize = 4 diff --git a/acorn-loose/src/parseutil.js b/acorn-loose/src/parseutil.js index 4501e2d46..534efd4b0 100644 --- a/acorn-loose/src/parseutil.js +++ b/acorn-loose/src/parseutil.js @@ -1 +1,3 @@ -export function isDummy(node) { return node.name === "✖" } +export const dummyValue = "✖" + +export function isDummy(node) { return node.name === dummyValue } diff --git a/acorn-loose/src/state.js b/acorn-loose/src/state.js index 52887231d..322306fe4 100644 --- a/acorn-loose/src/state.js +++ b/acorn-loose/src/state.js @@ -1,4 +1,5 @@ import {Parser, SourceLocation, tokTypes as tt, Node, lineBreak, isNewLine} from "acorn" +import {dummyValue} from "./parseutil" function noop() {} @@ -63,13 +64,13 @@ export class LooseParser { dummyIdent() { let dummy = this.dummyNode("Identifier") - dummy.name = "✖" + dummy.name = dummyValue return dummy } dummyString() { let dummy = this.dummyNode("Literal") - dummy.value = dummy.raw = "✖" + dummy.value = dummy.raw = dummyValue return dummy } diff --git a/acorn-loose/src/tokenize.js b/acorn-loose/src/tokenize.js index e838b48d8..da20b7daf 100644 --- a/acorn-loose/src/tokenize.js +++ b/acorn-loose/src/tokenize.js @@ -1,5 +1,6 @@ import {tokTypes as tt, Token, isNewLine, SourceLocation, getLineInfo, lineBreakG} from "acorn" import {LooseParser} from "./state" +import {dummyValue} from "./parseutil" const lp = LooseParser.prototype @@ -73,7 +74,7 @@ lp.readToken = function() { throw e } this.resetTo(pos) - if (replace === true) replace = {start: pos, end: pos, type: tt.name, value: "✖"} + if (replace === true) replace = {start: pos, end: pos, type: tt.name, value: dummyValue} if (replace) { if (this.options.locations) replace.loc = new SourceLocation( diff --git a/acorn-walk/CHANGELOG.md b/acorn-walk/CHANGELOG.md index 82d439902..89114970b 100644 --- a/acorn-walk/CHANGELOG.md +++ b/acorn-walk/CHANGELOG.md @@ -1,3 +1,15 @@ +## 7.1.1 (2020-02-13) + +### Bug fixes + +Clean up the type definitions to actually work well with the main parser. + +## 7.1.0 (2020-02-11) + +### New features + +Add a TypeScript definition file for the library. + ## 7.0.0 (2017-08-12) ### New features diff --git a/acorn-walk/dist/walk.d.ts b/acorn-walk/dist/walk.d.ts new file mode 100644 index 000000000..00cc005f1 --- /dev/null +++ b/acorn-walk/dist/walk.d.ts @@ -0,0 +1,112 @@ +import {Node} from 'acorn'; + +declare module "acorn-walk" { + type FullWalkerCallback = ( + node: Node, + state: TState, + type: string + ) => void; + + type FullAncestorWalkerCallback = ( + node: Node, + state: TState | Node[], + ancestors: Node[], + type: string + ) => void; + type WalkerCallback = (node: Node, state: TState) => void; + + type SimpleWalkerFn = ( + node: Node, + state: TState + ) => void; + + type AncestorWalkerFn = ( + node: Node, + state: TState| Node[], + ancestors: Node[] + ) => void; + + type RecursiveWalkerFn = ( + node: Node, + state: TState, + callback: WalkerCallback + ) => void; + + type SimpleVisitors = { + [type: string]: SimpleWalkerFn + }; + + type AncestorVisitors = { + [type: string]: AncestorWalkerFn + }; + + type RecursiveVisitors = { + [type: string]: RecursiveWalkerFn + }; + + type FindPredicate = (type: string, node: Node) => boolean; + + interface Found { + node: Node, + state: TState + } + + export function simple( + node: Node, + visitors: SimpleVisitors, + base?: RecursiveVisitors, + state?: TState + ): void; + + export function ancestor( + node: Node, + visitors: AncestorVisitors, + base?: RecursiveVisitors, + state?: TState + ): void; + + export function recursive( + node: Node, + state: TState, + functions: RecursiveVisitors, + base?: RecursiveVisitors + ): void; + + export function full( + node: Node, + callback: FullWalkerCallback, + base?: RecursiveVisitors, + state?: TState + ): void; + + export function fullAncestor( + node: Node, + callback: FullAncestorWalkerCallback, + base?: RecursiveVisitors, + state?: TState + ): void; + + export function make( + functions: RecursiveVisitors, + base?: RecursiveVisitors + ): RecursiveVisitors; + + export function findNodeAt( + node: Node, + start: number | undefined, + end?: number | undefined, + type?: FindPredicate | string, + base?: RecursiveVisitors, + state?: TState + ): Found | undefined; + + export function findNodeAround( + node: Node, + start: number | undefined, + type?: FindPredicate | string, + base?: RecursiveVisitors, + state?: TState + ): Found | undefined; + + export const findNodeAfter: typeof findNodeAround; +} diff --git a/acorn-walk/package.json b/acorn-walk/package.json index 375c21fc8..a66ca892c 100644 --- a/acorn-walk/package.json +++ b/acorn-walk/package.json @@ -3,8 +3,9 @@ "description": "ECMAScript (ESTree) AST walker", "homepage": "https://github.com/acornjs/acorn", "main": "dist/walk.js", + "types": "dist/walk.d.ts", "module": "dist/walk.mjs", - "version": "7.0.0", + "version": "7.1.1", "engines": {"node": ">=0.4.0"}, "maintainers": [ { diff --git a/acorn/CHANGELOG.md b/acorn/CHANGELOG.md index e893c221a..32f5ce8f3 100644 --- a/acorn/CHANGELOG.md +++ b/acorn/CHANGELOG.md @@ -1,3 +1,15 @@ +## 7.1.1 (2020-03-01) + +### Bug fixes + +Treat `\8` and `\9` as invalid escapes in template strings. + +Allow unicode escapes in property names that are keywords. + +Don't error on an exponential operator expression as argument to `await`. + +More carefully check for valid UTF16 surrogate pairs in regexp validator. + ## 7.1.0 (2019-09-24) ### Bug fixes diff --git a/acorn/package.json b/acorn/package.json index b9ff2b366..6a8f03607 100644 --- a/acorn/package.json +++ b/acorn/package.json @@ -3,8 +3,9 @@ "description": "ECMAScript parser", "homepage": "https://github.com/acornjs/acorn", "main": "dist/acorn.js", + "types": "dist/acorn.d.ts", "module": "dist/acorn.mjs", - "version": "7.1.0", + "version": "7.1.1", "engines": {"node": ">=0.4.0"}, "maintainers": [ { diff --git a/acorn/src/expression.js b/acorn/src/expression.js index de4ebd0c9..71b02016f 100644 --- a/acorn/src/expression.js +++ b/acorn/src/expression.js @@ -44,9 +44,11 @@ pp.checkPropClash = function(prop, propHash, refDestructuringErrors) { if (this.options.ecmaVersion >= 6) { if (name === "__proto__" && kind === "init") { if (propHash.proto) { - if (refDestructuringErrors && refDestructuringErrors.doubleProto < 0) refDestructuringErrors.doubleProto = key.start - // Backwards-compat kludge. Can be removed in version 6.0 - else this.raiseRecoverable(key.start, "Redefinition of __proto__ property") + if (refDestructuringErrors) { + if (refDestructuringErrors.doubleProto < 0) + refDestructuringErrors.doubleProto = key.start + // Backwards-compat kludge. Can be removed in version 6.0 + } else this.raiseRecoverable(key.start, "Redefinition of __proto__ property") } propHash.proto = true } @@ -111,12 +113,11 @@ pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) { else this.exprAllowed = false } - let ownDestructuringErrors = false, oldParenAssign = -1, oldTrailingComma = -1, oldShorthandAssign = -1 + let ownDestructuringErrors = false, oldParenAssign = -1, oldTrailingComma = -1 if (refDestructuringErrors) { oldParenAssign = refDestructuringErrors.parenthesizedAssign oldTrailingComma = refDestructuringErrors.trailingComma - oldShorthandAssign = refDestructuringErrors.shorthandAssign - refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = refDestructuringErrors.shorthandAssign = -1 + refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = -1 } else { refDestructuringErrors = new DestructuringErrors ownDestructuringErrors = true @@ -131,8 +132,11 @@ pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) { let node = this.startNodeAt(startPos, startLoc) node.operator = this.value node.left = this.type === tt.eq ? this.toAssignable(left, false, refDestructuringErrors) : left - if (!ownDestructuringErrors) DestructuringErrors.call(refDestructuringErrors) - refDestructuringErrors.shorthandAssign = -1 // reset because shorthand default was used correctly + if (!ownDestructuringErrors) { + refDestructuringErrors.parenthesizedAssign = refDestructuringErrors.trailingComma = refDestructuringErrors.doubleProto = -1 + } + if (refDestructuringErrors.shorthandAssign >= node.left.start) + refDestructuringErrors.shorthandAssign = -1 // reset because shorthand default was used correctly this.checkLVal(left) this.next() node.right = this.parseMaybeAssign(noIn) @@ -142,7 +146,6 @@ pp.parseMaybeAssign = function(noIn, refDestructuringErrors, afterLeftParse) { } if (oldParenAssign > -1) refDestructuringErrors.parenthesizedAssign = oldParenAssign if (oldTrailingComma > -1) refDestructuringErrors.trailingComma = oldTrailingComma - if (oldShorthandAssign > -1) refDestructuringErrors.shorthandAssign = oldShorthandAssign return left } @@ -247,8 +250,8 @@ pp.parseMaybeUnary = function(refDestructuringErrors, sawUnary) { pp.parseExprSubscripts = function(refDestructuringErrors) { let startPos = this.start, startLoc = this.startLoc let expr = this.parseExprAtom(refDestructuringErrors) - let skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")" - if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) return expr + if (expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")") + return expr let result = this.parseSubscripts(expr, startPos, startLoc) if (refDestructuringErrors && result.type === "MemberExpression") { if (refDestructuringErrors.parenthesizedAssign >= result.start) refDestructuringErrors.parenthesizedAssign = -1 @@ -546,6 +549,7 @@ pp.parseParenArrowList = function(startPos, startLoc, exprList) { const empty = [] pp.parseNew = function() { + if (this.containsEsc) this.raiseRecoverable(this.start, "Escape sequence in keyword new") let node = this.startNode() let meta = this.parseIdent(true) if (this.options.ecmaVersion >= 6 && this.eat(tt.dot)) { @@ -929,7 +933,7 @@ pp.parseIdent = function(liberal, isBinding) { } else { this.unexpected() } - this.next() + this.next(!!liberal) this.finishNode(node, "Identifier") if (!liberal) { this.checkUnreserved(node) @@ -961,6 +965,6 @@ pp.parseAwait = function() { let node = this.startNode() this.next() - node.argument = this.parseMaybeUnary(null, true) + node.argument = this.parseMaybeUnary(null, false) return this.finishNode(node, "AwaitExpression") } diff --git a/acorn/src/regexp.js b/acorn/src/regexp.js index ee19bcf55..605bce520 100644 --- a/acorn/src/regexp.js +++ b/acorn/src/regexp.js @@ -50,7 +50,8 @@ export class RegExpValidationState { if (!this.switchU || c <= 0xD7FF || c >= 0xE000 || i + 1 >= l) { return c } - return (c << 10) + s.charCodeAt(i + 1) - 0x35FDC00 + const next = s.charCodeAt(i + 1) + return next >= 0xDC00 && next <= 0xDFFF ? (c << 10) + next - 0x35FDC00 : c } nextIndex(i) { @@ -59,8 +60,9 @@ export class RegExpValidationState { if (i >= l) { return l } - const c = s.charCodeAt(i) - if (!this.switchU || c <= 0xD7FF || c >= 0xE000 || i + 1 >= l) { + let c = s.charCodeAt(i), next + if (!this.switchU || c <= 0xD7FF || c >= 0xE000 || i + 1 >= l || + (next = s.charCodeAt(i + 1)) < 0xDC00 || next > 0xDFFF) { return i + 1 } return i + 2 @@ -152,7 +154,7 @@ pp.regexp_pattern = function(state) { if (state.eat(0x29 /* ) */)) { state.raise("Unmatched ')'") } - if (state.eat(0x5D /* [ */) || state.eat(0x7D /* } */)) { + if (state.eat(0x5D /* ] */) || state.eat(0x7D /* } */)) { state.raise("Lone quantifier brackets") } } @@ -837,7 +839,7 @@ pp.regexp_eatCharacterClass = function(state) { if (state.eat(0x5B /* [ */)) { state.eat(0x5E /* ^ */) this.regexp_classRanges(state) - if (state.eat(0x5D /* [ */)) { + if (state.eat(0x5D /* ] */)) { return true } // Unreachable since it threw "unterminated regular expression" error before. @@ -885,7 +887,7 @@ pp.regexp_eatClassAtom = function(state) { } const ch = state.current() - if (ch !== 0x5D /* [ */) { + if (ch !== 0x5D /* ] */) { state.lastIntValue = ch state.advance() return true diff --git a/acorn/src/tokenize.js b/acorn/src/tokenize.js index f9f6b2f6f..835fdcd96 100644 --- a/acorn/src/tokenize.js +++ b/acorn/src/tokenize.js @@ -28,7 +28,9 @@ const pp = Parser.prototype // Move to the next token -pp.next = function() { +pp.next = function(ignoreEscapeSequenceInKeyword) { + if (!ignoreEscapeSequenceInKeyword && this.type.keyword && this.containsEsc) + this.raiseRecoverable(this.start, "Escape sequence in keyword " + this.type.keyword) if (this.options.onToken) this.options.onToken(new Token(this)) @@ -445,7 +447,6 @@ pp.readNumber = function(startsWithDot) { if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number") let octal = this.pos - start >= 2 && this.input.charCodeAt(start) === 48 if (octal && this.strict) this.raise(start, "Invalid number") - if (octal && /[89]/.test(this.input.slice(start, this.pos))) octal = false let next = this.input.charCodeAt(this.pos) if (!octal && !startsWithDot && this.options.ecmaVersion >= 11 && next === 110) { let str = this.input.slice(start, this.pos) @@ -454,6 +455,7 @@ pp.readNumber = function(startsWithDot) { if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number") return this.finishToken(tt.num, val) } + if (octal && /[89]/.test(this.input.slice(start, this.pos))) octal = false if (next === 46 && !octal) { // '.' ++this.pos this.readInt(10) @@ -628,6 +630,18 @@ pp.readEscapedChar = function(inTemplate) { case 10: // ' \n' if (this.options.locations) { this.lineStart = this.pos; ++this.curLine } return "" + case 56: + case 57: + if (inTemplate) { + const codePos = this.pos - 1 + + this.invalidStringToken( + codePos, + "Invalid escape sequence in template string" + ) + + return null + } default: if (ch >= 48 && ch <= 55) { let octalStr = this.input.substr(this.pos - 1, 3).match(/^[0-7]+/)[0] @@ -707,7 +721,6 @@ pp.readWord = function() { let word = this.readWord1() let type = tt.name if (this.keywords.test(word)) { - if (this.containsEsc) this.raiseRecoverable(this.start, "Escape sequence in keyword " + word) type = keywordTypes[word] } return this.finishToken(type, word) diff --git a/bin/run_test262.js b/bin/run_test262.js index c540507d7..6a2f9b05c 100644 --- a/bin/run_test262.js +++ b/bin/run_test262.js @@ -10,9 +10,12 @@ const unsupportedFeatures = [ "class-static-fields-private", "class-static-fields-public", "class-static-methods-private", + "coalesce-expression", "export-star-as-namespace-from-module", "import.meta", - "numeric-separator-literal" + "numeric-separator-literal", + "optional-chaining", + "top-level-await" ]; run( diff --git a/package.json b/package.json index eb0dd1c73..6d06debf8 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "eslint-plugin-standard": "^3.0.1", "rollup": "^1.7.0", "rollup-plugin-buble": "^0.19.0", - "test262": "git+https://github.com/tc39/test262.git#de567d3aa5de4eaa11e00131d26b9fe77997dfb0", + "test262": "git+https://github.com/tc39/test262.git#09380a4ae412e986ea39eb7163f5a6b3e5b04bbc", "test262-parser-runner": "^0.5.0", "test262-stream": "^1.2.1", "unicode-12.0.0": "^0.7.9" diff --git a/test/tests-asyncawait.js b/test/tests-asyncawait.js index 77de3c8b8..aebc6dc73 100644 --- a/test/tests-asyncawait.js +++ b/test/tests-asyncawait.js @@ -3523,3 +3523,5 @@ test( test("({ async delete() {} })", {}, {ecmaVersion: 8}) testFail("abc: async function a() {}", "Unexpected token (1:5)", {ecmaVersion: 8}) + +test("(async() => { await 4 ** 2 })()", {}, {ecmaVersion: 8}) diff --git a/test/tests-harmony.js b/test/tests-harmony.js index ade46a220..c6b58dd5c 100644 --- a/test/tests-harmony.js +++ b/test/tests-harmony.js @@ -16512,3 +16512,7 @@ test("let x = 1; x = 2", {}, {ecmaVersion: 6}) test("function *f2() { () => yield / 1 }", {}, {ecmaVersion: 6}) test("({ a = 42, b: c.d } = e)", {}, {ecmaVersion: 6}) + +testFail("({ a = 42, b: c = d })", "Shorthand property assignments are valid only in destructuring patterns (1:5)", {ecmaVersion: 6}) + +test("({ __proto__: x, __proto__: y, __proto__: z }) => {}", {}, {ecmaVersion: 6}) diff --git a/test/tests-regexp.js b/test/tests-regexp.js index 6c4719486..804e00a59 100644 --- a/test/tests-regexp.js +++ b/test/tests-regexp.js @@ -1049,6 +1049,7 @@ test("/[\\d][\\12-\\14]{1,}[^\\d]/", {}, { ecmaVersion: 2015 }) testFail("/[\\d][\\12-\\14]{1,}[^\\d]/u", "Invalid regular expression flag (1:1)", { ecmaVersion: 5 }) testFail("/[\\d][\\12-\\14]{1,}[^\\d]/u", "Invalid regular expression: /[\\d][\\12-\\14]{1,}[^\\d]/: Invalid class escape (1:1)", { ecmaVersion: 2015 }) test("/([a ]\\b)*\\b/", {}, { ecmaVersion: 5 }) +test("/[x-*]/u".replace("*", String.fromCharCode(0xd800)), {}, {ecmaVersion: 6}) /* // This is test case generator.