diff --git a/.circleci/config.yml b/.circleci/config.yml index 95de88499fc5..c43f8398678e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ jobs: - run: yarn --version - run: make test-ci-coverage # Builds babel-standalone with the regular Babel config - - run: make build + - run: IS_PUBLISH=true make build # test-ci-coverage doesn't test babel-standalone, as trying to gather coverage # data for a JS file that's several megabytes large is bound to fail. Here, # we just run the babel-standalone test separately. diff --git a/.editorconfig b/.editorconfig index ba48528bbf4b..d38c249a7aa9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,5 +10,8 @@ end_of_line = lf indent_style = space indent_size = 2 +[Makefile] +indent_style = tab + [*.{md,markdown}] trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json index 8a8366b60be9..a59dc0d9e2dd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,7 +14,8 @@ "rules": { "@babel/development/no-undefined-identifier": "error", "@babel/development/no-deprecated-clone": "error", - "import/no-extraneous-dependencies": "error" + "import/no-extraneous-dependencies": "error", + "guard-for-in": "error" } }, { diff --git a/.github/actions/trigger-github-release/Dockerfile b/.github/actions/trigger-github-release/Dockerfile index 69b3cb60f5e8..c7e17c775bae 100644 --- a/.github/actions/trigger-github-release/Dockerfile +++ b/.github/actions/trigger-github-release/Dockerfile @@ -12,6 +12,7 @@ ADD entrypoint.sh /action/entrypoint.sh ADD package.json /action/package.json ADD package-lock.json /action/package-lock.json ADD release.js /action/release.js +ADD update-changelog.js /action/update-changelog.js RUN chmod +x /action/entrypoint.sh diff --git a/.github/actions/trigger-github-release/entrypoint.sh b/.github/actions/trigger-github-release/entrypoint.sh index c5fcecaffb52..11c7f193e359 100755 --- a/.github/actions/trigger-github-release/entrypoint.sh +++ b/.github/actions/trigger-github-release/entrypoint.sh @@ -5,21 +5,18 @@ set -e echo "INFO: Installing action dependencies..." (cd /action; npm ci) -echo "INFO: Checking out current commit..." -git -c advice.detachedHead=false checkout $GITHUB_SHA - # GitHub doesn't support running actions on new tags yet: we need to run it on the commit. # For this reason, we can't be sure that the tag already exists. We can use the commit # message to create the tag. If the tag already exists locally, they won't conflict because # they have the same name and are on the same commit. echo "INFO: Getting release version..." -# current_tag=$(git describe --abbrev=0 --tags HEAD) -current_tag=$(git log --oneline --format=%B -1 HEAD) +# current_tag=$(git describe --abbrev=0 --tags $GITHUB_SHA) +current_tag=$(git log --oneline --format=%B -1 $GITHUB_SHA) echo "INFO: Creating new tag..." (git tag $current_tag $GITHUB_SHA) || echo "INFO: Tag already exists" -last_tag=$(git describe --abbrev=0 --tags HEAD^) +last_tag=$(git describe --abbrev=0 --tags $current_tag^) echo "INFO: New version is $current_tag; last version is $last_tag." echo "INFO: Generating the changelog..." @@ -35,4 +32,15 @@ changelog=$( echo "INFO: Publishing the new GitHub release..." echo "$changelog" | node /action/release $current_tag +echo "INFO: Updating CHANGELOG.md..." +echo "$changelog" | node /action/update-changelog + +echo "INFO: Committing changelog..." +git add CHANGELOG.md +git -c user.name="$COMMIT_AUTHOR_NAME" -c user.email="$COMMIT_AUTHOR_EMAIL" \ + commit -m "Add $current_tag to CHANGELOG.md [skip ci]" --no-verify --quiet + +echo "INFO: Pushing updates..." +git push "https://${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" master + echo "INFO: Done! Don't forget to thank new contributors :)" diff --git a/.github/actions/trigger-github-release/update-changelog.js b/.github/actions/trigger-github-release/update-changelog.js new file mode 100644 index 000000000000..f7fdd7fbdedd --- /dev/null +++ b/.github/actions/trigger-github-release/update-changelog.js @@ -0,0 +1,31 @@ +"use strict"; + +const getStdin = require("get-stdin"); +const fs = require("fs").promises; +const path = require("path"); + +const { GITHUB_WORKSPACE } = process.env; + +const INSERTION_POINT = ""; +const CHANGELOG = path.resolve(GITHUB_WORKSPACE, "CHANGELOG.md"); + +main(); +async function main() { + let [stdin, changelog] = await Promise.all([ + getStdin(), + fs.readFile(CHANGELOG, "utf8"), + ]); + + if (!changelog.includes(INSERTION_POINT)) { + throw new Error(`Missing "${INSERTION_POINT}" in CHANGELOG.md`); + } + + // Remove committers + stdin = stdin.split("\n\n#### Committers")[0]; + changelog = changelog.replace( + INSERTION_POINT, + INSERTION_POINT + "\n" + stdin + ); + + await fs.writeFile(CHANGELOG, changelog); +} diff --git a/.github/main.workflow b/.github/main.workflow index bb88d85128d8..1a6c5a4058d2 100644 --- a/.github/main.workflow +++ b/.github/main.workflow @@ -7,6 +7,11 @@ action "Trigger GitHub release" { uses = "./.github/actions/trigger-github-release/" secrets = ["GITHUB_TOKEN"] + env = { + COMMIT_AUTHOR_NAME = "Babel Bot" + COMMIT_AUTHOR_EMAIL = "babel@hopeinsource.com" + } + # When GitHub Actions will support the "release" event for public # repositories, we won't need these checks anymore. needs = [ diff --git a/CHANGELOG.md b/CHANGELOG.md index ec87f4f4176a..f0a65682a747 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,37 @@ See [CHANGELOG - v4](/.github/CHANGELOG-v4.md), [CHANGELOG - v5](/.github/CHANGE See [CHANGELOG - 6to5](/.github/CHANGELOG-6to5.md) for the pre-4.0.0 version changelog. See [Babylon's CHANGELOG](packages/babylon/CHANGELOG.md) for the Babylon pre-7.0.0-beta.29 version changelog. + + + +## v7.3.4 (2019-02-25) + +#### :bug: Bug Fix +* `babel-parser` + * [#9572](https://github.com/babel/babel/pull/9572) Fix TypeScript parsers missing token check (#9571) ([@elevatebart](https://github.com/elevatebart)) + * [#9521](https://github.com/babel/babel/pull/9521) Also check AssignmentPatterns for duplicate export name ([@danez](https://github.com/danez)) +* `babel-helper-create-class-features-plugin`, `babel-helper-replace-supers`, `babel-plugin-proposal-class-properties`, `babel-traverse` + * [#9508](https://github.com/babel/babel/pull/9508) Use correct "this" in static fields ([@nicolo-ribaudo](https://github.com/nicolo-ribaudo)) +* `babel-preset-env` + * [#9566](https://github.com/babel/babel/pull/9566) Closes [#9465](https://github.com/babel/babel/issues/9465) ([@zloirock](https://github.com/zloirock)) +* `babel-types` + * [#9539](https://github.com/babel/babel/pull/9539) babel-types is* type checks accept null | undefiend as value TS type ([@ian-craig](https://github.com/ian-craig)) +* `babel-plugin-transform-block-scoping`, `babel-traverse` + * [#9532](https://github.com/babel/babel/pull/9532) Migrate some duplicate binding tests to traverse ([@danez](https://github.com/danez)) +* `babel-generator` + * [#9524](https://github.com/babel/babel/pull/9524) Fix typescript generator params ([@tanhauhau](https://github.com/tanhauhau)) + * [#9523](https://github.com/babel/babel/pull/9523) Fix flow babel-generator function parantheses ([@tanhauhau](https://github.com/tanhauhau)) + +#### :house: Internal +* Other + * [#9561](https://github.com/babel/babel/pull/9561) Update CHANGELOG.md using the "Trigger GitHub release" action ([@nicolo-ribaudo](https://github.com/nicolo-ribaudo)) +* `babel-plugin-proposal-object-rest-spread`, `babel-plugin-transform-modules-systemjs` + * [#9541](https://github.com/babel/babel/pull/9541) Enable eqeqeq rule in eslint ([@danez](https://github.com/danez)) +* `babel-generator`, `babel-parser`, `babel-plugin-transform-flow-strip-types`, `babel-traverse` + * [#9522](https://github.com/babel/babel/pull/9522) Make tests spec compliant by avoiding duplicate declarations in input files ([@danez](https://github.com/danez)) +* `babel-plugin-transform-proto-to-assign` + * [#9533](https://github.com/babel/babel/pull/9533) Add import/no-extraneous-dependencies to ESLint ([@nicolo-ribaudo](https://github.com/nicolo-ribaudo)) + ## v7.3.3 (2019-02-15) #### :eyeglasses: Spec Compliancy diff --git a/Gulpfile.js b/Gulpfile.js index acada36eea3d..105d37a8ac0f 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -12,9 +12,7 @@ const gulp = require("gulp"); const path = require("path"); const webpack = require("webpack"); const merge = require("merge-stream"); -const rollup = require("rollup-stream"); -const source = require("vinyl-source-stream"); -const buffer = require("vinyl-buffer"); +const rollup = require("rollup"); const rollupBabel = require("rollup-plugin-babel"); const rollupNodeResolve = require("rollup-plugin-node-resolve"); const { registerStandalonePackageTask } = require("./scripts/gulp-tasks"); @@ -35,13 +33,9 @@ function getIndexFromPackage(name) { return `${name}/src/index.js`; } -function compilationLogger(rollup) { +function compilationLogger() { return through.obj(function(file, enc, callback) { - fancyLog( - `Compiling '${chalk.cyan(file.relative)}'${ - rollup ? " with rollup " : "" - }...` - ); + fancyLog(`Compiling '${chalk.cyan(file.relative)}'...`); callback(null, file); }); } @@ -90,32 +84,36 @@ function buildBabel(exclude) { } function buildRollup(packages) { - return merge( + return Promise.all( packages.map(pkg => { - return rollup({ - input: getIndexFromPackage(pkg), - format: "cjs", - plugins: [ - rollupBabel({ - envName: "babel-parser", - }), - rollupNodeResolve(), - ], - }) - .pipe(source("index.js")) - .pipe(buffer()) - .pipe(errorsLogger()) - .pipe(compilationLogger(/* rollup */ true)) - .pipe(gulp.dest(path.join(pkg, "lib"))); + const input = getIndexFromPackage(pkg); + fancyLog(`Compiling '${chalk.cyan(input)}' with rollup ...`); + return rollup + .rollup({ + input, + plugins: [ + rollupBabel({ + envName: "babel-parser", + }), + rollupNodeResolve(), + ], + }) + .then(bundle => { + return bundle.write({ + file: path.join(pkg, "lib/index.js"), + format: "cjs", + name: "babel-parser", + }); + }); }) ); } -gulp.task("build", function() { - const bundles = ["packages/babel-parser"]; +const bundles = ["packages/babel-parser"]; - return merge([buildBabel(/* exclude */ bundles), buildRollup(bundles)]); -}); +gulp.task("build-rollup", () => buildRollup(bundles)); +gulp.task("build-babel", () => buildBabel(/* exclude */ bundles)); +gulp.task("build", gulp.parallel("build-rollup", "build-babel")); gulp.task("default", gulp.series("build")); diff --git a/Makefile b/Makefile index 67adb8378d29..1a52c4cef887 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ MAKEFLAGS = -j1 FLOW_COMMIT = 2ac56861e3ceff9ca406ae586fbafb3480c6c0b7 -TEST262_COMMIT = 4f1155c566a222238fd86f179c6635ecb4c289bb +TEST262_COMMIT = b4e15b3d5cf63571151dbd02c0987864544c6a56 # Fix color output until TravisCI fixes https://github.com/travis-ci/travis-ci/issues/7967 export FORCE_COLOR = true @@ -11,6 +11,7 @@ SOURCES = packages codemods build: clean clean-lib ./node_modules/.bin/gulp build + node ./packages/babel-standalone/scripts/generate.js node ./packages/babel-types/scripts/generateTypeHelpers.js # call build again as the generated files might need to be compiled again. ./node_modules/.bin/gulp build @@ -103,7 +104,7 @@ test-flow-update-whitelist: bootstrap-test262: rm -rf ./build/test262 mkdir -p ./build - git clone --branch=master --single-branch --shallow-since=2010-01-10 https://github.com/tc39/test262.git ./build/test262 + git clone --branch=master --single-branch --shallow-since=2019-01-01 https://github.com/tc39/test262.git ./build/test262 cd build/test262 && git checkout $(TEST262_COMMIT) test-test262: diff --git a/babel.config.js b/babel.config.js index 12f4bb16ca83..cb8f2fbf003b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -115,9 +115,11 @@ module.exports = function(api) { "packages/babel-runtime", /[\\/]node_modules[\\/](?:@babel\/runtime|babel-runtime|core-js)[\\/]/, ], - plugins: [includeRuntime ? "@babel/transform-runtime" : null].filter( - Boolean - ), + plugins: [ + includeRuntime + ? ["@babel/transform-runtime", { version: "7.3.4" }] + : null, + ].filter(Boolean), }, ].filter(Boolean), }; diff --git a/eslint-local-rules.js b/eslint-local-rules.js deleted file mode 100644 index 8b3b7ea4971f..000000000000 --- a/eslint-local-rules.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; - -const noDeprecatedClone = require("./scripts/eslint_rules/no-deprecated-clone"); -const noUndefinedIdentifier = require("./scripts/eslint_rules/no-undefined-identifier"); -const pluginName = require("./scripts/eslint_rules/plugin-name"); - -module.exports = { - "no-deprecated-clone": noDeprecatedClone, - "no-undefined-identifier": noUndefinedIdentifier, - "plugin-name": pluginName, -}; diff --git a/lerna.json b/lerna.json index ce4edadaea0a..8e4c852552ed 100644 --- a/lerna.json +++ b/lerna.json @@ -1,10 +1,10 @@ { - "version": "7.3.3", + "version": "7.3.4", "changelog": { "repo": "babel/babel", "cacheDir": ".changelog", "labels": { - "PR: Spec Compliancy :eyeglasses:": ":eyeglasses: Spec Compliancy", + "PR: Spec Compliance :eyeglasses:": ":eyeglasses: Spec Compliance", "PR: Breaking Change :boom:": ":boom: Breaking Change", "PR: New Feature :rocket:": ":rocket: New Feature", "PR: Bug Fix :bug:": ":bug: Bug Fix", diff --git a/package.json b/package.json index d682ce4540bf..4100dbd0147f 100644 --- a/package.json +++ b/package.json @@ -10,19 +10,19 @@ }, "devDependencies": { "@babel/cli": "^7.2.3", - "@babel/core": "^7.2.2", + "@babel/core": "^7.3.4", "@babel/eslint-plugin-development": "^1.0.1", - "@babel/plugin-proposal-class-properties": "^7.3.0", + "@babel/plugin-proposal-class-properties": "^7.3.4", "@babel/plugin-proposal-export-namespace-from": "^7.2.0", "@babel/plugin-proposal-numeric-separator": "^7.2.0", "@babel/plugin-transform-modules-commonjs": "^7.2.0", - "@babel/plugin-transform-runtime": "^7.2.0", - "@babel/preset-env": "^7.3.1", + "@babel/plugin-transform-runtime": "^7.3.4", + "@babel/preset-env": "^7.3.4", "@babel/preset-flow": "^7.0.0", "@babel/register": "^7.0.0", - "@babel/runtime": "^7.3.1", + "@babel/runtime": "^7.3.4", "babel-eslint": "^11.0.0-beta.0", - "babel-jest": "^24.0.0", + "babel-jest": "^24.5.0", "babel-loader": "^8.0.5", "babel-plugin-transform-charcodes": "^0.2.0", "browserify": "^16.2.3", @@ -32,13 +32,13 @@ "derequire": "^2.0.2", "duplicate-package-checker-webpack-plugin": "^2.1.0", "enhanced-resolve": "^3.0.0", - "eslint": "^5.12.1", - "eslint-config-babel": "^8.0.2", + "eslint": "^5.15.1", + "eslint-config-babel": "^9.0.0", "eslint-plugin-flowtype": "^3.2.1", "eslint-plugin-import": "^2.16.0", "eslint-plugin-prettier": "^3.0.1", "fancy-log": "^1.3.3", - "flow-bin": "^0.92.1", + "flow-bin": "^0.94.0", "graceful-fs": "^4.1.15", "gulp": "^4.0.0", "gulp-babel": "^8.0.0", @@ -46,10 +46,10 @@ "gulp-newer": "^1.0.0", "gulp-plumber": "^1.2.1", "gulp-rename": "^1.4.0", - "gulp-uglify": "^3.0.1", + "gulp-uglify": "^3.0.2", "gulp-watch": "^5.0.1", "husky": "^1.3.1", - "jest": "^24.0.0", + "jest": "^24.5.0", "lerna": "^3.6.0", "lerna-changelog": "^0.5.0", "lint-staged": "^8.1.0", @@ -59,13 +59,12 @@ "prettier": "^1.16.1", "pump": "^3.0.0", "rimraf": "^2.6.3", - "rollup-plugin-babel": "^4.0.0-beta.0", - "rollup-plugin-node-resolve": "^3.0.2", - "rollup-stream": "^1.24.1", + "rollup": "^1.6.0", + "rollup-plugin-babel": "^4.0.0", + "rollup-plugin-node-resolve": "^4.0.1", "test262-stream": "^1.2.0", "through2": "^2.0.0", - "vinyl-buffer": "^1.0.1", - "vinyl-source-stream": "^2.0.0", + "warnings-to-errors-webpack-plugin": "^2.0.0", "webpack": "^3.4.1", "webpack-dependency-suite": "^2.4.4", "webpack-stream": "^4.0.0" diff --git a/packages/babel-core/package.json b/packages/babel-core/package.json index a3820f8a760d..d07beafc0c8b 100644 --- a/packages/babel-core/package.json +++ b/packages/babel-core/package.json @@ -1,6 +1,6 @@ { "name": "@babel/core", - "version": "7.3.3", + "version": "7.3.4", "description": "Babel compiler core.", "main": "lib/index.js", "author": "Sebastian McKenzie ", @@ -34,12 +34,12 @@ }, "dependencies": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.3.3", + "@babel/generator": "^7.3.4", "@babel/helpers": "^7.2.0", - "@babel/parser": "^7.3.3", + "@babel/parser": "^7.3.4", "@babel/template": "^7.2.2", - "@babel/traverse": "^7.2.2", - "@babel/types": "^7.3.3", + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4", "convert-source-map": "^1.1.0", "debug": "^4.1.0", "json5": "^2.1.0", diff --git a/packages/babel-generator/package.json b/packages/babel-generator/package.json index ed5860c96e80..85fe45c82400 100644 --- a/packages/babel-generator/package.json +++ b/packages/babel-generator/package.json @@ -1,6 +1,6 @@ { "name": "@babel/generator", - "version": "7.3.3", + "version": "7.3.4", "description": "Turns an AST into code.", "author": "Sebastian McKenzie ", "homepage": "https://babeljs.io/", @@ -14,7 +14,7 @@ "lib" ], "dependencies": { - "@babel/types": "^7.3.3", + "@babel/types": "^7.3.4", "jsesc": "^2.5.1", "lodash": "^4.17.11", "source-map": "^0.5.0", @@ -22,6 +22,6 @@ }, "devDependencies": { "@babel/helper-fixtures": "^7.2.0", - "@babel/parser": "^7.3.3" + "@babel/parser": "^7.3.4" } } diff --git a/packages/babel-generator/src/generators/base.js b/packages/babel-generator/src/generators/base.js index cb98733e5541..dd45014377f6 100644 --- a/packages/babel-generator/src/generators/base.js +++ b/packages/babel-generator/src/generators/base.js @@ -82,3 +82,13 @@ export function DirectiveLiteral(node: Object) { export function InterpreterDirective(node: Object) { this.token(`#!${node.value}\n`); } + +export function Placeholder(node: Object) { + this.token("%%"); + this.print(node.name); + this.token("%%"); + + if (node.expectedNode === "Statement") { + this.semicolon(); + } +} diff --git a/packages/babel-generator/src/generators/types.js b/packages/babel-generator/src/generators/types.js index 491ae7513828..fdd090dd4113 100644 --- a/packages/babel-generator/src/generators/types.js +++ b/packages/babel-generator/src/generators/types.js @@ -7,6 +7,10 @@ export function Identifier(node: Object) { }); } +export function ArgumentPlaceholder() { + this.token("?"); +} + export function RestElement(node: Object) { this.token("..."); this.print(node.argument, node); diff --git a/packages/babel-generator/src/printer.js b/packages/babel-generator/src/printer.js index 1d0b2077a160..fb4715d491c5 100644 --- a/packages/babel-generator/src/printer.js +++ b/packages/babel-generator/src/printer.js @@ -367,7 +367,7 @@ export default class Printer { const loc = t.isProgram(node) || t.isFile(node) ? null : node.loc; this.withSource("start", loc, () => { - this[node.type](node, parent); + printMethod.call(this, node, parent); }); this._printTrailingComments(node); diff --git a/packages/babel-generator/test/fixtures/edgecase/single-arg-async-arrow-with-retainlines/input.js b/packages/babel-generator/test/fixtures/edgecase/single-arg-async-arrow-with-retainlines/input.js index e409b79f575f..b5a205cc36d6 100644 --- a/packages/babel-generator/test/fixtures/edgecase/single-arg-async-arrow-with-retainlines/input.js +++ b/packages/babel-generator/test/fixtures/edgecase/single-arg-async-arrow-with-retainlines/input.js @@ -2,8 +2,8 @@ var fn = async ( arg ) => {} -async (x) -=> {} +async (x) => +{} async x => {} diff --git a/packages/babel-generator/test/fixtures/edgecase/unary-op/options.json b/packages/babel-generator/test/fixtures/edgecase/unary-op/options.json new file mode 100644 index 000000000000..a2e80d9ec294 --- /dev/null +++ b/packages/babel-generator/test/fixtures/edgecase/unary-op/options.json @@ -0,0 +1,3 @@ +{ + "strictMode": false +} diff --git a/packages/babel-generator/test/fixtures/flow/declare-exports/input.js b/packages/babel-generator/test/fixtures/flow/declare-exports/input.js index ea4a8c7cd463..97544d82b930 100644 --- a/packages/babel-generator/test/fixtures/flow/declare-exports/input.js +++ b/packages/babel-generator/test/fixtures/flow/declare-exports/input.js @@ -17,6 +17,7 @@ declare export * from 'asd'; declare export { a, b }; declare export {}; declare export { c, d } from 'bar'; +var a, b; declare module B { declare export type B = {}; @@ -24,9 +25,9 @@ declare module B { } declare module "foo" { declare export type * from "bar"; } -declare export opaque type Foo; +declare export opaque type Foo1; declare export opaque type Bar; declare export opaque type Baz: Foo; -declare export opaque type Foo: Bar; -declare export opaque type Foo: Bar; -declare export opaque type Foo: Bar; +declare export opaque type Foo3: Bar; +declare export opaque type Foo4: Bar; +declare export opaque type Foo5: Bar; diff --git a/packages/babel-generator/test/fixtures/flow/declare-exports/output.js b/packages/babel-generator/test/fixtures/flow/declare-exports/output.js index da22c014154d..59f3a4c56856 100644 --- a/packages/babel-generator/test/fixtures/flow/declare-exports/output.js +++ b/packages/babel-generator/test/fixtures/flow/declare-exports/output.js @@ -26,6 +26,7 @@ declare export * from 'asd'; declare export { a, b }; declare export {}; declare export { c, d } from 'bar'; +var a, b; declare module B { declare export type B = {}; declare export interface Moon {} @@ -33,9 +34,9 @@ declare module B { declare module "foo" { declare export type * from "bar"; } -declare export opaque type Foo; +declare export opaque type Foo1; declare export opaque type Bar; declare export opaque type Baz: Foo; -declare export opaque type Foo: Bar; -declare export opaque type Foo: Bar; -declare export opaque type Foo: Bar; \ No newline at end of file +declare export opaque type Foo3: Bar; +declare export opaque type Foo4: Bar; +declare export opaque type Foo5: Bar; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/flow/def-site-variance/input.js b/packages/babel-generator/test/fixtures/flow/def-site-variance/input.js index 5cda68f8e9c5..9f00e80993d1 100644 --- a/packages/babel-generator/test/fixtures/flow/def-site-variance/input.js +++ b/packages/babel-generator/test/fixtures/flow/def-site-variance/input.js @@ -1,10 +1,10 @@ class C1<+T, -U> {} function f<+T, -U>() {} type T<+T, -U> = {}; -type T = { +p: T }; -type T = { -p: T }; -type T = { +[k:K]: V }; -type T = { -[k:K]: V }; +type T2 = { +p: T }; +type T3 = { -p: T }; +type T4 = { +[k:K]: V }; +type T5 = { -[k:K]: V }; interface I { +p: T } interface I { -p: T } interface I { +[k:K]: V } diff --git a/packages/babel-generator/test/fixtures/flow/def-site-variance/output.js b/packages/babel-generator/test/fixtures/flow/def-site-variance/output.js index e411f4646133..5c2b69d1a6dd 100644 --- a/packages/babel-generator/test/fixtures/flow/def-site-variance/output.js +++ b/packages/babel-generator/test/fixtures/flow/def-site-variance/output.js @@ -3,16 +3,16 @@ class C1<+T, -U> {} function f<+T, -U>() {} type T<+T, -U> = {}; -type T = { +type T2 = { +p: T }; -type T = { +type T3 = { -p: T }; -type T = { +type T4 = { +[k: K]: V }; -type T = { +type T5 = { -[k: K]: V }; interface I { diff --git a/packages/babel-generator/test/fixtures/flow/internal-slot/input.js b/packages/babel-generator/test/fixtures/flow/internal-slot/input.js index 7888fc6cb43e..b00d3bd76f22 100644 --- a/packages/babel-generator/test/fixtures/flow/internal-slot/input.js +++ b/packages/babel-generator/test/fixtures/flow/internal-slot/input.js @@ -1,7 +1,7 @@ declare class C { static [[foo]]: T } declare class C { [[foo]]: T } -interface T { [[foo]]: X } -interface T { [[foo]](): X } -type T = { [[foo]]: X } -type T = { [[foo]](): X } -type T = { [[foo]]?: X } +interface I { [[foo]]: X } +interface I { [[foo]](): X } +type T1 = { [[foo]]: X } +type T2 = { [[foo]](): X } +type T3 = { [[foo]]?: X } diff --git a/packages/babel-generator/test/fixtures/flow/internal-slot/output.js b/packages/babel-generator/test/fixtures/flow/internal-slot/output.js index a8c375a2c456..2a48fb9f186a 100644 --- a/packages/babel-generator/test/fixtures/flow/internal-slot/output.js +++ b/packages/babel-generator/test/fixtures/flow/internal-slot/output.js @@ -4,18 +4,18 @@ declare class C { declare class C { [[foo]]: T } -interface T { +interface I { [[foo]]: X } -interface T { +interface I { [[foo]]() => X } -type T = { +type T1 = { [[foo]]: X }; -type T = { +type T2 = { [[foo]]() => X }; -type T = { +type T3 = { [[foo]]?: X -}; +}; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/flow/object-literal-types/input.js b/packages/babel-generator/test/fixtures/flow/object-literal-types/input.js index 6d747ee65e10..f3139a94e350 100644 --- a/packages/babel-generator/test/fixtures/flow/object-literal-types/input.js +++ b/packages/babel-generator/test/fixtures/flow/object-literal-types/input.js @@ -1,11 +1,11 @@ type U = {}; type V = {}; -type T = { ...U, }; -type T = { ...U, ...V }; -type T = { p: V, ...U }; -type T = { ...U, p: V, }; -type T = { ...{}|{ p: V, }}; -type T = { foo(): number } -type T = { foo: () => number } -type T = { [string]: U }; -type T = { [param: string]: U }; +type T1 = { ...U, }; +type T2 = { ...U, ...V }; +type T3 = { p: V, ...U }; +type T4 = { ...U, p: V, }; +type T5 = { ...{}|{ p: V, }}; +type T6 = { foo(): number } +type T7 = { foo: () => number } +type T8 = { [string]: U }; +type T9 = { [param: string]: U }; diff --git a/packages/babel-generator/test/fixtures/flow/object-literal-types/output.js b/packages/babel-generator/test/fixtures/flow/object-literal-types/output.js index 952f62e3022e..5c02e095e3fa 100644 --- a/packages/babel-generator/test/fixtures/flow/object-literal-types/output.js +++ b/packages/babel-generator/test/fixtures/flow/object-literal-types/output.js @@ -1,30 +1,30 @@ type U = {}; type V = {}; -type T = { ...U +type T1 = { ...U }; -type T = { ...U, +type T2 = { ...U, ...V, }; -type T = { +type T3 = { p: V, ...U, }; -type T = { ...U, +type T4 = { ...U, p: V, }; -type T = { ...{} | { +type T5 = { ...{} | { p: V } }; -type T = { +type T6 = { foo(): number }; -type T = { +type T7 = { foo: () => number }; -type T = { +type T8 = { [string]: U }; -type T = { +type T9 = { [param: string]: U }; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/flow/opaque-type-alias/input.js b/packages/babel-generator/test/fixtures/flow/opaque-type-alias/input.js index 1988fcaeed71..3e77fce2b1ca 100644 --- a/packages/babel-generator/test/fixtures/flow/opaque-type-alias/input.js +++ b/packages/babel-generator/test/fixtures/flow/opaque-type-alias/input.js @@ -1,7 +1,7 @@ opaque type ID = string; opaque type Foo = Bar; opaque type Maybe = _Maybe; -export opaque type Foo = number; +export opaque type Foo2 = number; opaque type union = | {type: "A"} diff --git a/packages/babel-generator/test/fixtures/flow/opaque-type-alias/output.js b/packages/babel-generator/test/fixtures/flow/opaque-type-alias/output.js index b4f20f2d705e..293f54010947 100644 --- a/packages/babel-generator/test/fixtures/flow/opaque-type-alias/output.js +++ b/packages/babel-generator/test/fixtures/flow/opaque-type-alias/output.js @@ -1,7 +1,7 @@ opaque type ID = string; opaque type Foo = Bar; opaque type Maybe = _Maybe; -export opaque type Foo = number; +export opaque type Foo2 = number; opaque type union = { type: "A" } | { diff --git a/packages/babel-generator/test/fixtures/flow/type-alias/input.js b/packages/babel-generator/test/fixtures/flow/type-alias/input.js index f71342c5265f..c1d567e6dfdd 100644 --- a/packages/babel-generator/test/fixtures/flow/type-alias/input.js +++ b/packages/babel-generator/test/fixtures/flow/type-alias/input.js @@ -1,7 +1,7 @@ type FBID = number; type Foo = Bar; type Maybe = _Maybe; -export type Foo = number; +export type Foo2 = number; type union = | {type: "A"} diff --git a/packages/babel-generator/test/fixtures/flow/type-alias/output.js b/packages/babel-generator/test/fixtures/flow/type-alias/output.js index a8263f67218b..51933a7a774e 100644 --- a/packages/babel-generator/test/fixtures/flow/type-alias/output.js +++ b/packages/babel-generator/test/fixtures/flow/type-alias/output.js @@ -1,7 +1,7 @@ type FBID = number; type Foo = Bar; type Maybe = _Maybe; -export type Foo = number; +export type Foo2 = number; type union = { type: "A" } | { diff --git a/packages/babel-generator/test/fixtures/flow/type-annotations/input.js b/packages/babel-generator/test/fixtures/flow/type-annotations/input.js index 76be1b446fac..e7335c30b72a 100644 --- a/packages/babel-generator/test/fixtures/flow/type-annotations/input.js +++ b/packages/babel-generator/test/fixtures/flow/type-annotations/input.js @@ -107,7 +107,7 @@ import { type Foo12 } from "bar"; import { typeof Foo13 } from "bar"; import { type Foo as Bar1 } from "bar"; import { typeof Foo as Bar2 } from "bar"; -export type { foo }; +export type { foo1 }; export type { bar } from "bar"; export interface baz { p: number } export interface qux { p: T } diff --git a/packages/babel-generator/test/fixtures/flow/type-annotations/output.js b/packages/babel-generator/test/fixtures/flow/type-annotations/output.js index cd69710614a1..6465359f336d 100644 --- a/packages/babel-generator/test/fixtures/flow/type-annotations/output.js +++ b/packages/babel-generator/test/fixtures/flow/type-annotations/output.js @@ -231,7 +231,7 @@ import { type Foo12 } from "bar"; import { typeof Foo13 } from "bar"; import { type Foo as Bar1 } from "bar"; import { typeof Foo as Bar2 } from "bar"; -export type { foo }; +export type { foo1 }; export type { bar } from "bar"; export interface baz { p: number @@ -293,4 +293,4 @@ function foo27(numVal: number = 2) {} function foo28(numVal?: number = 2) {} -export type * from "foo"; +export type * from "foo"; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/harmony-edgecase/exports/input.js b/packages/babel-generator/test/fixtures/harmony-edgecase/exports/input.js index ec680f05ac1d..540245d565b3 100644 --- a/packages/babel-generator/test/fixtures/harmony-edgecase/exports/input.js +++ b/packages/babel-generator/test/fixtures/harmony-edgecase/exports/input.js @@ -1,3 +1,4 @@ +var a, c; export * from "OK" export { name } from "OK" export { a as b, c as d } from "hello" diff --git a/packages/babel-generator/test/fixtures/harmony-edgecase/exports/output.js b/packages/babel-generator/test/fixtures/harmony-edgecase/exports/output.js index 5482292b19a7..84c968fe12a3 100644 --- a/packages/babel-generator/test/fixtures/harmony-edgecase/exports/output.js +++ b/packages/babel-generator/test/fixtures/harmony-edgecase/exports/output.js @@ -1,3 +1,4 @@ +var a, c; export * from "OK"; export { name } from "OK"; export { a as b, c as d } from "hello"; diff --git a/packages/babel-generator/test/fixtures/misc/placeholders/input.js b/packages/babel-generator/test/fixtures/misc/placeholders/input.js new file mode 100644 index 000000000000..42eb25727b64 --- /dev/null +++ b/packages/babel-generator/test/fixtures/misc/placeholders/input.js @@ -0,0 +1,8 @@ +var %%a%% = %%b%% + +%%c%% + +class %%d%% {} +class A %%e%% + +function %%f%%(...%%g%%) %%h%% diff --git a/packages/babel-generator/test/fixtures/misc/placeholders/options.json b/packages/babel-generator/test/fixtures/misc/placeholders/options.json new file mode 100644 index 000000000000..92404e501295 --- /dev/null +++ b/packages/babel-generator/test/fixtures/misc/placeholders/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["placeholders"] +} diff --git a/packages/babel-generator/test/fixtures/misc/placeholders/output.js b/packages/babel-generator/test/fixtures/misc/placeholders/output.js new file mode 100644 index 000000000000..82ad54ecc61e --- /dev/null +++ b/packages/babel-generator/test/fixtures/misc/placeholders/output.js @@ -0,0 +1,8 @@ +var %%a%% = %%b%%; +%%c%%; + +class %%d%% {} + +class A %%e%% + +function %%f%%(...%%g%%) %%h%% \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/input.js b/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/input.js new file mode 100644 index 000000000000..29e9b90f83e0 --- /dev/null +++ b/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/input.js @@ -0,0 +1,12 @@ +foo(?); +foo(?, x); +foo(x, ?); +foo(?, x, ?); +obj.foo(x, ?); +obj.foo(?, x); +obj.foo(?, x, ?); +class foo { + constructor() { + baz(this, () => super.bar(?)); + } +} diff --git a/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/options.json b/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/options.json new file mode 100644 index 000000000000..eff064512269 --- /dev/null +++ b/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/options.json @@ -0,0 +1 @@ +{ "plugins": ["partialApplication"] } diff --git a/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/output.js b/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/output.js new file mode 100644 index 000000000000..7cb1b16bd55e --- /dev/null +++ b/packages/babel-generator/test/fixtures/types/ArgumentPlaceholder/output.js @@ -0,0 +1,14 @@ +foo(?); +foo(?, x); +foo(x, ?); +foo(?, x, ?); +obj.foo(x, ?); +obj.foo(?, x); +obj.foo(?, x, ?); + +class foo { + constructor() { + baz(this, () => super.bar(?)); + } + +} \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/input.js b/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/input.js index e1a7074425ea..ae3f4b33c003 100644 --- a/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/input.js +++ b/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/input.js @@ -20,6 +20,16 @@ class Foo { static get foo() {} static set foo(bar) {} static static() {} + static * foo() {} + static async * foo() {} + + static #foo() {} + static async #foo() {} + static ["foo"]() {} + static get #foo() {} + static set #foo(taz) {} + static * #foo() {} + static async * #foo() {} get () {} diff --git a/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/output.js b/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/output.js index fbb5a3097e73..24f3e9229c84 100644 --- a/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/output.js +++ b/packages/babel-generator/test/fixtures/types/ClassBody-MethodDefinition/output.js @@ -37,6 +37,24 @@ class Foo { static static() {} + static *foo() {} + + static async *foo() {} + + static #foo() {} + + static async #foo() {} + + static ["foo"]() {} + + static get #foo() {} + + static set #foo(taz) {} + + static *#foo() {} + + static async *#foo() {} + get() {} set() {} diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier11/input.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier11/input.js index f40ac5c3434e..701c971b8991 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier11/input.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier11/input.js @@ -1 +1,2 @@ export { foo }; +var foo; diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier11/output.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier11/output.js index f604032cb6d6..520c0fb1dda1 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier11/output.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier11/output.js @@ -1 +1,2 @@ -export { foo }; \ No newline at end of file +export { foo }; +var foo; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier12/input.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier12/input.js index 9dfa348be312..5e6c6606032a 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier12/input.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier12/input.js @@ -1 +1,2 @@ export { foo, bar }; +var foo, bar; diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier12/output.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier12/output.js index 4cb44a4b2592..be6d973b815d 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier12/output.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier12/output.js @@ -1 +1,2 @@ -export { foo, bar }; \ No newline at end of file +export { foo, bar }; +var foo, bar; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier13/input.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier13/input.js index e4cca384daec..dd1cfea6d77d 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier13/input.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier13/input.js @@ -1 +1,2 @@ export { foo as bar }; +var foo; diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier13/output.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier13/output.js index 5d4569c099d2..d6e7ac50254e 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier13/output.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier13/output.js @@ -1 +1,2 @@ -export { foo as bar }; \ No newline at end of file +export { foo as bar }; +var foo; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier14/input.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier14/input.js index 95daad9d9676..5e840fff920b 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier14/input.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier14/input.js @@ -1 +1,2 @@ export { foo as default }; +var foo; diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier14/output.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier14/output.js index f96da82b3b2c..4a3a9fe44ea7 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier14/output.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier14/output.js @@ -1 +1,2 @@ -export { foo as default }; \ No newline at end of file +export { foo as default }; +var foo; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier15/input.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier15/input.js index ebd14b2d969e..022457fd87a6 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier15/input.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier15/input.js @@ -1 +1,2 @@ export { foo as default, bar }; +var foo, bar; diff --git a/packages/babel-generator/test/fixtures/types/ExportSpecifier15/output.js b/packages/babel-generator/test/fixtures/types/ExportSpecifier15/output.js index 2ef73817d596..974c22ae14f2 100644 --- a/packages/babel-generator/test/fixtures/types/ExportSpecifier15/output.js +++ b/packages/babel-generator/test/fixtures/types/ExportSpecifier15/output.js @@ -1 +1,2 @@ -export { foo as default, bar }; \ No newline at end of file +export { foo as default, bar }; +var foo, bar; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/types/UnaryExpression/options.json b/packages/babel-generator/test/fixtures/types/UnaryExpression/options.json new file mode 100644 index 000000000000..a2e80d9ec294 --- /dev/null +++ b/packages/babel-generator/test/fixtures/types/UnaryExpression/options.json @@ -0,0 +1,3 @@ +{ + "strictMode": false +} diff --git a/packages/babel-generator/test/fixtures/types/WithStatement/options.json b/packages/babel-generator/test/fixtures/types/WithStatement/options.json new file mode 100644 index 000000000000..a2e80d9ec294 --- /dev/null +++ b/packages/babel-generator/test/fixtures/types/WithStatement/options.json @@ -0,0 +1,3 @@ +{ + "strictMode": false +} diff --git a/packages/babel-generator/test/index.js b/packages/babel-generator/test/index.js index fc20e8ad425e..4781339b28ea 100644 --- a/packages/babel-generator/test/index.js +++ b/packages/babel-generator/test/index.js @@ -489,7 +489,7 @@ suites.forEach(function(testSuite) { const actualAst = parse(actualCode, { filename: actual.loc, plugins: task.options.plugins || [], - strictMode: false, + strictMode: task.options.strictMode === false ? false : true, sourceType: "module", sourceMaps: !!task.sourceMap, ...task.options.parserOpts, diff --git a/packages/babel-helper-call-delegate/src/index.js b/packages/babel-helper-call-delegate/src/index.js index 8aee0276132c..d4bf041d0131 100644 --- a/packages/babel-helper-call-delegate/src/index.js +++ b/packages/babel-helper-call-delegate/src/index.js @@ -41,7 +41,7 @@ export default function(path: NodePath, scope = path.scope) { path.traverse(visitor, state); - if (state.foundArguments) { + if (state.foundArguments || state.foundThis) { callee = t.memberExpression(container, t.identifier("apply")); args = []; diff --git a/packages/babel-helper-create-class-features-plugin/package.json b/packages/babel-helper-create-class-features-plugin/package.json index 81056b4bef10..54f3d95dd86b 100644 --- a/packages/babel-helper-create-class-features-plugin/package.json +++ b/packages/babel-helper-create-class-features-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@babel/helper-create-class-features-plugin", - "version": "7.3.2", + "version": "7.3.4", "author": "The Babel Team (https://babeljs.io/team)", "license": "MIT", "description": "Compile class public and private fields, private methods and decorators to ES6", @@ -18,14 +18,14 @@ "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.2.3", + "@babel/helper-replace-supers": "^7.3.4", "@babel/helper-split-export-declaration": "^7.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" }, "devDependencies": { - "@babel/core": "^7.2.2", + "@babel/core": "^7.3.4", "@babel/helper-plugin-test-runner": "^7.0.0" } } diff --git a/packages/babel-helper-create-class-features-plugin/src/features.js b/packages/babel-helper-create-class-features-plugin/src/features.js index 5ad2cf26948a..fffc0d0014e0 100644 --- a/packages/babel-helper-create-class-features-plugin/src/features.js +++ b/packages/babel-helper-create-class-features-plugin/src/features.js @@ -67,9 +67,9 @@ export function verifyUsedFeatures(path, file) { throw path.buildCodeFrameError("Class private methods are not enabled."); } - if (path.node.static) { + if (path.node.static && path.node.kind !== "method") { throw path.buildCodeFrameError( - "@babel/plugin-class-features doesn't support class static private methods yet.", + "@babel/plugin-class-features doesn't support class static private accessors yet.", ); } } diff --git a/packages/babel-helper-create-class-features-plugin/src/fields.js b/packages/babel-helper-create-class-features-plugin/src/fields.js index de121f9cf5dd..e29ff33776ca 100644 --- a/packages/babel-helper-create-class-features-plugin/src/fields.js +++ b/packages/babel-helper-create-class-features-plugin/src/fields.js @@ -24,7 +24,7 @@ export function buildPrivateNamesMap(props) { update.getId = prop.scope.generateUidIdentifier(`get_${name}`); } else if (prop.node.kind === "set") { update.setId = prop.scope.generateUidIdentifier(`set_${name}`); - } else if (prop.node.kind === "method" && isMethod && isInstance) { + } else if (prop.node.kind === "method") { update.methodId = prop.scope.generateUidIdentifier(name); } privateNamesMap.set(name, update); @@ -141,12 +141,18 @@ const privateNameHandlerSpec = { setId, } = privateNamesMap.get(name); - if (isStatic && !isMethod) { - return t.callExpression( - file.addHelper("classStaticPrivateFieldSpecGet"), - [this.receiver(member), t.cloneNode(classRef), t.cloneNode(id)], - ); + if (isStatic) { + const helperName = isMethod + ? "classStaticPrivateMethodGet" + : "classStaticPrivateFieldSpecGet"; + + return t.callExpression(file.addHelper(helperName), [ + this.receiver(member), + t.cloneNode(classRef), + t.cloneNode(id), + ]); } + if (isMethod) { if (getId || setId) { return t.callExpression(file.addHelper("classPrivateFieldGet"), [ @@ -176,11 +182,17 @@ const privateNameHandlerSpec = { setId, } = privateNamesMap.get(name); - if (isStatic && !isMethod) { - return t.callExpression( - file.addHelper("classStaticPrivateFieldSpecSet"), - [this.receiver(member), t.cloneNode(classRef), t.cloneNode(id), value], - ); + if (isStatic) { + const helperName = isMethod + ? "classStaticPrivateMethodSet" + : "classStaticPrivateFieldSpecSet"; + + return t.callExpression(file.addHelper(helperName), [ + this.receiver(member), + t.cloneNode(classRef), + t.cloneNode(id), + value, + ]); } if (isMethod) { if (setId) { @@ -403,14 +415,28 @@ function buildPublicFieldInitSpec(ref, prop, state) { ); } -function buildPrivateInstanceMethodDeclaration(prop, privateNamesMap) { +function buildPrivateStaticMethodInitLoose(ref, prop, state, privateNamesMap) { + const { id, methodId } = privateNamesMap.get(prop.node.key.id.name); + return template.statement.ast` + Object.defineProperty(${ref}, ${id}, { + // configurable is false by default + // enumerable is false by default + // writable is false by default + value: ${methodId.name} + }); + `; +} + +function buildPrivateMethodDeclaration(prop, privateNamesMap, loose = false) { const privateName = privateNamesMap.get(prop.node.key.id.name); const { + id, methodId, getId, setId, getterDeclared, setterDeclared, + static: isStatic, } = privateName; const { params, body, generator, async } = prop.node; const methodValue = t.functionExpression( @@ -441,6 +467,14 @@ function buildPrivateInstanceMethodDeclaration(prop, privateNamesMap) { t.variableDeclarator(setId, methodValue), ]); } + if (isStatic && !loose) { + return t.variableDeclaration("var", [ + t.variableDeclarator( + id, + t.functionExpression(id, params, body, generator, async), + ), + ]); + } return t.variableDeclaration("var", [ t.variableDeclarator(methodId, methodValue), @@ -472,8 +506,9 @@ function replaceThisContext(path, ref, superRef, file, loose) { }); replacer.isStatic = true; replacer.replace(); - - path.traverse(thisContextVisitor, state); + if (path.isProperty()) { + path.traverse(thisContextVisitor, state); + } return state.needsClassRef; } @@ -497,7 +532,7 @@ export function buildFieldsInitNodes( const isField = prop.isProperty(); const isMethod = !isField; - if (isStatic && isField) { + if (isStatic) { const replaced = replaceThisContext(prop, ref, superRef, state, loose); needsClassRef = needsClassRef || replaced; } @@ -548,7 +583,7 @@ export function buildFieldsInitNodes( ), ); staticNodes.push( - buildPrivateInstanceMethodDeclaration(prop, privateNamesMap), + buildPrivateMethodDeclaration(prop, privateNamesMap, loose), ); break; case isInstance && isPrivate && isMethod && !loose: @@ -560,7 +595,27 @@ export function buildFieldsInitNodes( ), ); staticNodes.push( - buildPrivateInstanceMethodDeclaration(prop, privateNamesMap), + buildPrivateMethodDeclaration(prop, privateNamesMap, loose), + ); + break; + case isStatic && isPrivate && isMethod && !loose: + needsClassRef = true; + staticNodes.push( + buildPrivateMethodDeclaration(prop, privateNamesMap, loose), + ); + break; + case isStatic && isPrivate && isMethod && loose: + needsClassRef = true; + staticNodes.push( + buildPrivateMethodDeclaration(prop, privateNamesMap, loose), + ); + staticNodes.push( + buildPrivateStaticMethodInitLoose( + t.cloneNode(ref), + prop, + state, + privateNamesMap, + ), ); break; case isInstance && isPublic && isField && loose: diff --git a/packages/babel-helper-define-map/src/index.js b/packages/babel-helper-define-map/src/index.js index ff588c1ce380..da81b3a43bd7 100644 --- a/packages/babel-helper-define-map/src/index.js +++ b/packages/babel-helper-define-map/src/index.js @@ -98,7 +98,7 @@ export function push( } export function hasComputed(mutatorMap: Object): boolean { - for (const key in mutatorMap) { + for (const key of Object.keys(mutatorMap)) { if (mutatorMap[key]._computed) { return true; } diff --git a/packages/babel-helper-hoist-variables/src/index.js b/packages/babel-helper-hoist-variables/src/index.js index 4f9c6d938bd7..6b8bb4cf3b03 100644 --- a/packages/babel-helper-hoist-variables/src/index.js +++ b/packages/babel-helper-hoist-variables/src/index.js @@ -28,7 +28,7 @@ const visitor = { ); } - for (const name in declar.getBindingIdentifiers()) { + for (const name of Object.keys(declar.getBindingIdentifiers())) { state.emit(t.identifier(name), name, declar.node.init !== null); } } diff --git a/packages/babel-helper-replace-supers/package.json b/packages/babel-helper-replace-supers/package.json index 309e682b3abe..1bc336576a3a 100644 --- a/packages/babel-helper-replace-supers/package.json +++ b/packages/babel-helper-replace-supers/package.json @@ -1,6 +1,6 @@ { "name": "@babel/helper-replace-supers", - "version": "7.2.3", + "version": "7.3.4", "description": "Helper function to replace supers", "repository": "https://github.com/babel/babel/tree/master/packages/babel-helper-replace-supers", "license": "MIT", @@ -11,7 +11,7 @@ "dependencies": { "@babel/helper-member-expression-to-functions": "^7.0.0", "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.2.3", - "@babel/types": "^7.0.0" + "@babel/traverse": "^7.3.4", + "@babel/types": "^7.3.4" } } diff --git a/packages/babel-helper-split-export-declaration/src/index.js b/packages/babel-helper-split-export-declaration/src/index.js index ffbf52243a05..c591f8746396 100644 --- a/packages/babel-helper-split-export-declaration/src/index.js +++ b/packages/babel-helper-split-export-declaration/src/index.js @@ -49,10 +49,7 @@ export default function splitExportDeclaration(exportDeclaration) { exportDeclaration.replaceWith(updatedDeclaration); if (needBindingRegistration) { - scope.registerBinding( - isClassDeclaration ? "let" : "var", - exportDeclaration, - ); + scope.registerDeclaration(exportDeclaration); } return exportDeclaration; diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 7d2cbf4f03d6..2448e0cdeaae 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -1102,7 +1102,21 @@ helpers.classStaticPrivateFieldSpecSet = helper("7.0.2")` descriptor.value = value; return value; } - +`; + +helpers.classStaticPrivateMethodGet = helper("7.3.2")` + export default function _classStaticPrivateMethodGet(receiver, classConstructor, method) { + if (receiver !== classConstructor) { + throw new TypeError("Private static access of wrong provenance"); + } + return method; + } +`; + +helpers.classStaticPrivateMethodSet = helper("7.3.2")` + export default function _classStaticPrivateMethodSet() { + throw new TypeError("attempted to set read only static private field"); + } `; helpers.decorate = helper("7.1.5")` diff --git a/packages/babel-parser/ast/spec.md b/packages/babel-parser/ast/spec.md index 31afe89aab02..34c04919cbd0 100644 --- a/packages/babel-parser/ast/spec.md +++ b/packages/babel-parser/ast/spec.md @@ -72,6 +72,7 @@ These are the core @babel/parser (babylon) AST node types. - [LogicalExpression](#logicalexpression) - [LogicalOperator](#logicaloperator) - [SpreadElement](#spreadelement) + - [ArgumentPlaceholder](#argumentplaceholder) - [MemberExpression](#memberexpression) - [BindExpression](#bindexpression) - [ConditionalExpression](#conditionalexpression) @@ -863,6 +864,14 @@ interface SpreadElement <: Node { } ``` +### ArgumentPlaceholder + +```js +interface ArgumentPlaceholder <: Node { + type: "ArgumentPlaceholder"; +} +``` + ### MemberExpression ```js diff --git a/packages/babel-parser/package.json b/packages/babel-parser/package.json index 555014a52b19..c93d2cfbeac8 100644 --- a/packages/babel-parser/package.json +++ b/packages/babel-parser/package.json @@ -1,6 +1,6 @@ { "name": "@babel/parser", - "version": "7.3.3", + "version": "7.3.4", "description": "A JavaScript parser", "author": "Sebastian McKenzie ", "homepage": "https://babeljs.io/", @@ -30,8 +30,8 @@ "devDependencies": { "@babel/code-frame": "^7.0.0", "@babel/helper-fixtures": "^7.2.0", - "charcodes": "0.1.0", - "unicode-11.0.0": "^0.7.8" + "charcodes": "^0.2.0", + "unicode-12.0.0": "^0.7.9" }, "bin": { "parser": "./bin/babel-parser.js" diff --git a/packages/babel-parser/scripts/generate-identifier-regex.js b/packages/babel-parser/scripts/generate-identifier-regex.js index 083adcde0a2a..21e249ad4f28 100644 --- a/packages/babel-parser/scripts/generate-identifier-regex.js +++ b/packages/babel-parser/scripts/generate-identifier-regex.js @@ -1,7 +1,8 @@ "use strict"; -// Which Unicode version should be used? -const version = "11.0.0"; +// Always use the latest available version of Unicode! +// https://tc39.github.io/ecma262/#sec-conformance +const version = "12.0.0"; const start = require("unicode-" + version + diff --git a/packages/babel-parser/src/options.js b/packages/babel-parser/src/options.js index 347c1099b2a0..5a615ccddfac 100755 --- a/packages/babel-parser/src/options.js +++ b/packages/babel-parser/src/options.js @@ -65,7 +65,7 @@ export const defaultOptions: Options = { export function getOptions(opts: ?Options): Options { const options: any = {}; - for (const key in defaultOptions) { + for (const key of Object.keys(defaultOptions)) { options[key] = opts && opts[key] != null ? opts[key] : defaultOptions[key]; } return options; diff --git a/packages/babel-parser/src/parser/base.js b/packages/babel-parser/src/parser/base.js index a56058c7c2b7..f2eeaaa01465 100644 --- a/packages/babel-parser/src/parser/base.js +++ b/packages/babel-parser/src/parser/base.js @@ -3,17 +3,23 @@ import type { Options } from "../options"; import type State from "../tokenizer/state"; import type { PluginsMap } from "./index"; +import type ScopeHandler from "../util/scope"; export default class BaseParser { // Properties set by constructor in index.js options: Options; inModule: boolean; + scope: ScopeHandler; plugins: PluginsMap; filename: ?string; sawUnambiguousESM: boolean = false; // Initialized by Tokenizer state: State; + // input and length are not in state as they are constant and we do + // not want to ever copy them, which happens if state gets cloned + input: string; + length: number; hasPlugin(name: string): boolean { return this.plugins.has(name); diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 427c8683c45e..3f8ca135c529 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -29,10 +29,29 @@ import { } from "../util/identifier"; import type { Pos, Position } from "../util/location"; import * as charCodes from "charcodes"; +import { + BIND_OUTSIDE, + BIND_VAR, + functionFlags, + SCOPE_ARROW, + SCOPE_CLASS, + SCOPE_DIRECT_SUPER, + SCOPE_SUPER, + SCOPE_PROGRAM, +} from "../util/scopeflags"; + +const unwrapParenthesizedExpression = node => { + return node.type === "ParenthesizedExpression" + ? unwrapParenthesizedExpression(node.expression) + : node; +}; export default class ExpressionParser extends LValParser { // Forward-declaration: defined in statement.js - +parseBlock: (allowDirectives?: boolean) => N.BlockStatement; + +parseBlock: ( + allowDirectives?: boolean, + createNewLexicalScope?: boolean, + ) => N.BlockStatement; +parseClass: ( node: N.Class, isStatement: boolean, @@ -41,10 +60,9 @@ export default class ExpressionParser extends LValParser { +parseDecorators: (allowExport?: boolean) => void; +parseFunction: ( node: T, - isStatement: boolean, + statement?: number, allowExpressionBody?: boolean, isAsync?: boolean, - optionalId?: boolean, ) => T; +parseFunctionParams: (node: N.Function, allowModifiers?: boolean) => void; +takeDecorators: (node: N.HasDecorators) => void; @@ -55,10 +73,18 @@ export default class ExpressionParser extends LValParser { // strict mode, init properties are also not allowed to be repeated. checkPropClash( - prop: N.ObjectMember, + prop: N.ObjectMember | N.SpreadElement, propHash: { [key: string]: boolean }, ): void { - if (prop.computed || prop.kind) return; + if ( + prop.type === "SpreadElement" || + prop.computed || + prop.kind || + // $FlowIgnore + prop.shorthand + ) { + return; + } const key = prop.key; // It is either an Identifier or a String/NumericLiteral @@ -74,6 +100,7 @@ export default class ExpressionParser extends LValParser { // Convenience method to parse an Expression only getExpression(): N.Expression { + this.scope.enter(SCOPE_PROGRAM); this.nextToken(); const expr = this.parseExpression(); if (!this.match(tt.eof)) { @@ -128,7 +155,7 @@ export default class ExpressionParser extends LValParser { const startPos = this.state.start; const startLoc = this.state.startLoc; if (this.isContextual("yield")) { - if (this.state.inGenerator) { + if (this.scope.inGenerator) { let left = this.parseYield(noIn); if (afterLeftParse) { left = afterLeftParse.call(this, left, startPos, startLoc); @@ -183,21 +210,13 @@ export default class ExpressionParser extends LValParser { this.checkLVal(left, undefined, undefined, "assignment expression"); + const maybePattern = unwrapParenthesizedExpression(left); + let patternErrorMsg; - let elementName; - - const unwrap = node => { - return node.type === "ParenthesizedExpression" - ? unwrap(node.expression) - : node; - }; - const maybePattern = unwrap(left); if (maybePattern.type === "ObjectPattern") { patternErrorMsg = "`({a}) = 0` use `({a} = 0)`"; - elementName = "property"; } else if (maybePattern.type === "ArrayPattern") { patternErrorMsg = "`([a]) = 0` use `([a] = 0)`"; - elementName = "element"; } if ( @@ -211,7 +230,7 @@ export default class ExpressionParser extends LValParser { ); } - if (elementName) this.checkCommaAfterRestFromSpread(elementName); + if (patternErrorMsg) this.checkCommaAfterRestFromSpread(); this.state.commaAfterSpreadAt = oldCommaAfterSpreadAt; this.next(); @@ -316,7 +335,6 @@ export default class ExpressionParser extends LValParser { const operator = this.state.value; node.left = left; node.operator = operator; - if ( operator === "**" && left.type === "UnaryExpression" && @@ -348,7 +366,7 @@ export default class ExpressionParser extends LValParser { if ( this.match(tt.name) && this.state.value === "await" && - this.state.inAsync + this.scope.inAsync ) { throw this.raise( this.state.start, @@ -438,8 +456,8 @@ export default class ExpressionParser extends LValParser { parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression { if ( this.isContextual("await") && - (this.state.inAsync || - (!this.state.inFunction && this.options.allowAwaitOutsideFunction)) + (this.scope.inAsync || + (!this.scope.inFunction && this.options.allowAwaitOutsideFunction)) ) { return this.parseAwait(); } else if (this.state.type.prefix) { @@ -524,12 +542,21 @@ export default class ExpressionParser extends LValParser { startLoc: Position, noCalls?: ?boolean, ): N.Expression { + const maybeAsyncArrow = this.atPossibleAsync(base); + const state = { optionalChainMember: false, stop: false, }; do { - base = this.parseSubscript(base, startPos, startLoc, noCalls, state); + base = this.parseSubscript( + base, + startPos, + startLoc, + noCalls, + state, + maybeAsyncArrow, + ); } while (!state.stop); return base; } @@ -544,6 +571,7 @@ export default class ExpressionParser extends LValParser { startLoc: Position, noCalls: ?boolean, state: N.ParseSubscriptState, + maybeAsyncArrow: boolean, ): N.Expression { if (!noCalls && this.eat(tt.doubleColon)) { const node = this.startNodeAt(startPos, startLoc); @@ -575,13 +603,8 @@ export default class ExpressionParser extends LValParser { this.expect(tt.bracketR); return this.finishNode(node, "OptionalMemberExpression"); } else if (this.eat(tt.parenL)) { - const possibleAsync = this.atPossibleAsync(base); - node.callee = base; - node.arguments = this.parseCallExpressionArguments( - tt.parenR, - possibleAsync, - ); + node.arguments = this.parseCallExpressionArguments(tt.parenR, false); node.optional = true; return this.finishNode(node, "OptionalCallExpression"); } else { @@ -620,7 +643,6 @@ export default class ExpressionParser extends LValParser { this.state.yieldPos = 0; this.state.awaitPos = 0; - const possibleAsync = this.atPossibleAsync(base); this.next(); let node = this.startNodeAt(startPos, startLoc); @@ -631,8 +653,9 @@ export default class ExpressionParser extends LValParser { node.arguments = this.parseCallExpressionArguments( tt.parenR, - possibleAsync, + maybeAsyncArrow, base.type === "Import", + base.type !== "Super", ); if (!state.optionalChainMember) { this.finishCallExpression(node); @@ -640,10 +663,10 @@ export default class ExpressionParser extends LValParser { this.finishOptionalCallExpression(node); } - if (possibleAsync && this.shouldParseAsyncArrow()) { + if (maybeAsyncArrow && this.shouldParseAsyncArrow()) { state.stop = true; - this.checkCommaAfterRestFromSpread("parameter"); + this.checkCommaAfterRestFromSpread(); node = this.parseAsyncArrowFromCallExpression( this.startNodeAt(startPos, startLoc), @@ -703,11 +726,11 @@ export default class ExpressionParser extends LValParser { atPossibleAsync(base: N.Expression): boolean { return ( - !this.state.containsEsc && - this.state.potentialArrowAt === base.start && base.type === "Identifier" && base.name === "async" && - !this.canInsertSemicolon() + this.state.lastTokEnd === base.end && + !this.canInsertSemicolon() && + this.input.slice(base.start, base.end) === "async" ); } @@ -743,6 +766,7 @@ export default class ExpressionParser extends LValParser { close: TokenType, possibleAsyncArrow: boolean, dynamicImport?: boolean, + allowPlaceholder?: boolean, ): $ReadOnlyArray { const elts = []; let innerParenStart; @@ -775,6 +799,7 @@ export default class ExpressionParser extends LValParser { false, possibleAsyncArrow ? { start: 0 } : undefined, possibleAsyncArrow ? { start: 0 } : undefined, + allowPlaceholder, ), ); } @@ -788,7 +813,7 @@ export default class ExpressionParser extends LValParser { } shouldParseAsyncArrow(): boolean { - return this.match(tt.arrow); + return this.match(tt.arrow) && !this.canInsertSemicolon(); } parseAsyncArrowFromCallExpression( @@ -823,11 +848,7 @@ export default class ExpressionParser extends LValParser { switch (this.state.type) { case tt._super: - if ( - !this.state.inMethod && - !this.state.inClassProperty && - !this.options.allowSuperOutsideMethod - ) { + if (!this.scope.allowSuper && !this.options.allowSuperOutsideMethod) { this.raise( this.state.start, "super is only allowed in object methods and classes", @@ -836,24 +857,26 @@ export default class ExpressionParser extends LValParser { node = this.startNode(); this.next(); - if ( - !this.match(tt.parenL) && - !this.match(tt.bracketL) && - !this.match(tt.dot) - ) { - this.unexpected(); - } if ( this.match(tt.parenL) && - this.state.inMethod !== "constructor" && + !this.scope.allowDirectSuper && !this.options.allowSuperOutsideMethod ) { this.raise( node.start, - "super() is only valid inside a class constructor. " + - "Make sure the method name is spelled exactly as 'constructor'.", + "super() is only valid inside a class constructor of a subclass. " + + "Maybe a typo in the method name ('constructor') or not extending another class?", ); } + + if ( + !this.match(tt.parenL) && + !this.match(tt.bracketL) && + !this.match(tt.dot) + ) { + this.unexpected(); + } + return this.finishNode(node, "Super"); case tt._import: @@ -887,20 +910,18 @@ export default class ExpressionParser extends LValParser { !this.canInsertSemicolon() ) { this.next(); - return this.parseFunction(node, false, false, true); + return this.parseFunction(node, undefined, true); } else if ( canBeArrow && + !containsEsc && id.name === "async" && this.match(tt.name) && !this.canInsertSemicolon() ) { - const oldInAsync = this.state.inAsync; - this.state.inAsync = true; const params = [this.parseIdentifier()]; this.expect(tt.arrow); // let foo = async bar => {}; this.parseArrowExpression(node, params, true); - this.state.inAsync = oldInAsync; return node; } @@ -917,12 +938,9 @@ export default class ExpressionParser extends LValParser { this.expectPlugin("doExpressions"); const node = this.startNode(); this.next(); - const oldInFunction = this.state.inFunction; const oldLabels = this.state.labels; this.state.labels = []; - this.state.inFunction = false; - node.body = this.parseBlock(false); - this.state.inFunction = oldInFunction; + node.body = this.parseBlock(); this.state.labels = oldLabels; return this.finishNode(node, "DoExpression"); } @@ -1053,19 +1071,8 @@ export default class ExpressionParser extends LValParser { if (isPrivate) { this.expectOnePlugin(["classPrivateProperties", "classPrivateMethods"]); const node = this.startNode(); - const columnHashEnd = this.state.end; this.next(); - const columnIdentifierStart = this.state.start; - - const spacesBetweenHashAndIdentifier = - columnIdentifierStart - columnHashEnd; - if (spacesBetweenHashAndIdentifier != 0) { - this.raise( - columnIdentifierStart, - "Unexpected space between # and identifier", - ); - } - + this.assertNoSpace("Unexpected space between # and identifier"); node.id = this.parseIdentifier(true); return this.finishNode(node, "PrivateName"); } else { @@ -1085,10 +1092,10 @@ export default class ExpressionParser extends LValParser { this.next(); meta = this.createIdentifier(meta, "function"); - if (this.state.inGenerator && this.eat(tt.dot)) { + if (this.scope.inGenerator && this.eat(tt.dot)) { return this.parseMetaProperty(node, meta, "sent"); } - return this.parseFunction(node, false); + return this.parseFunction(node); } parseMetaProperty( @@ -1162,11 +1169,7 @@ export default class ExpressionParser extends LValParser { const node = this.startNodeAt(startPos, startLoc); this.addExtra(node, "rawValue", value); - this.addExtra( - node, - "raw", - this.state.input.slice(startPos, this.state.end), - ); + this.addExtra(node, "raw", this.input.slice(startPos, this.state.end)); node.value = value; this.next(); return this.finishNode(node, type); @@ -1212,13 +1215,13 @@ export default class ExpressionParser extends LValParser { spreadStart = this.state.start; exprList.push( this.parseParenItem( - this.parseRest(), + this.parseRestBinding(), spreadNodeStartPos, spreadNodeStartLoc, ), ); - this.checkCommaAfterRest(tt.parenR, "parameter"); + this.checkCommaAfterRest(); break; } else { @@ -1325,7 +1328,7 @@ export default class ExpressionParser extends LValParser { if (this.eat(tt.dot)) { const metaProp = this.parseMetaProperty(node, meta, "target"); - if (!this.state.inFunction && !this.state.inClassProperty) { + if (!this.scope.inNonArrowFunction && !this.state.inClassProperty) { let error = "new.target can only be used in functions"; if (this.hasPlugin("classProperties")) { @@ -1388,7 +1391,7 @@ export default class ExpressionParser extends LValParser { } } elem.value = { - raw: this.state.input + raw: this.input .slice(this.state.start, this.state.end) .replace(/\r\n?/g, "\n"), cooked: this.state.value, @@ -1420,7 +1423,6 @@ export default class ExpressionParser extends LValParser { isPattern: boolean, refShorthandDefaultPos?: ?Pos, ): T { - let decorators = []; const propHash: any = Object.create(null); let first = true; const node = this.startNode(); @@ -1436,108 +1438,117 @@ export default class ExpressionParser extends LValParser { if (this.eat(tt.braceR)) break; } - if (this.match(tt.at)) { - if (this.hasPlugin("decorators")) { - this.raise( - this.state.start, - "Stage 2 decorators disallow object literal property decorators", - ); - } else { - // we needn't check if decorators (stage 0) plugin is enabled since it's checked by - // the call to this.parseDecorator - while (this.match(tt.at)) { - decorators.push(this.parseDecorator()); - } - } - } - - let prop = this.startNode(), - isGenerator = false, - isAsync = false, - startPos, - startLoc; - if (decorators.length) { - prop.decorators = decorators; - decorators = []; - } + const prop = this.parseObjectMember(isPattern, refShorthandDefaultPos); + // $FlowIgnore RestElement will never be returned if !isPattern + if (!isPattern) this.checkPropClash(prop, propHash); - if (this.match(tt.ellipsis)) { - prop = this.parseSpread(isPattern ? { start: 0 } : undefined); - node.properties.push(prop); - if (isPattern) { - this.toAssignable(prop, true, "object pattern"); - this.checkCommaAfterRest(tt.braceR, "property"); - this.expect(tt.braceR); - break; - } - continue; - } - - prop.method = false; - - if (isPattern || refShorthandDefaultPos) { - startPos = this.state.start; - startLoc = this.state.startLoc; + // $FlowIgnore + if (prop.shorthand) { + this.addExtra(prop, "shorthand", true); } - if (!isPattern) { - isGenerator = this.eat(tt.star); - } + node.properties.push(prop); + } - const containsEsc = this.state.containsEsc; + return this.finishNode( + node, + isPattern ? "ObjectPattern" : "ObjectExpression", + ); + } - if (!isPattern && this.isContextual("async")) { - if (isGenerator) this.unexpected(); + isAsyncProp(prop: N.ObjectProperty): boolean { + return ( + !prop.computed && + prop.key.type === "Identifier" && + prop.key.name === "async" && + (this.match(tt.name) || + this.match(tt.num) || + this.match(tt.string) || + this.match(tt.bracketL) || + this.state.type.keyword || + this.match(tt.star)) && + !this.hasPrecedingLineBreak() + ); + } - 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) - ) { - prop.key = asyncId; - prop.computed = false; - } else { - isAsync = true; - isGenerator = this.eat(tt.star); - this.parsePropertyName(prop); - } + parseObjectMember( + isPattern: boolean, + refShorthandDefaultPos: ?Pos, + ): N.ObjectMember | N.SpreadElement | N.RestElement { + let decorators = []; + if (this.match(tt.at)) { + if (this.hasPlugin("decorators")) { + this.raise( + this.state.start, + "Stage 2 decorators disallow object literal property decorators", + ); } else { - this.parsePropertyName(prop); + // we needn't check if decorators (stage 0) plugin is enabled since it's checked by + // the call to this.parseDecorator + while (this.match(tt.at)) { + decorators.push(this.parseDecorator()); + } } + } - this.parseObjPropValue( - prop, - startPos, - startLoc, - isGenerator, - isAsync, - isPattern, - refShorthandDefaultPos, - containsEsc, - ); - this.checkPropClash(prop, propHash); + const prop = this.startNode(); + let isGenerator = false; + let isAsync = false; + let startPos; + let startLoc; - if (prop.shorthand) { - this.addExtra(prop, "shorthand", true); + if (this.match(tt.ellipsis)) { + if (decorators.length) this.unexpected(); + if (isPattern) { + this.next(); + // Don't use parseRestBinding() as we only allow Identifier here. + prop.argument = this.parseIdentifier(); + this.checkCommaAfterRest(); + return this.finishNode(prop, "RestElement"); } - node.properties.push(prop); + return this.parseSpread(); } if (decorators.length) { - this.raise( - this.state.start, - "You have trailing decorators with no property", - ); + prop.decorators = decorators; + decorators = []; } - return this.finishNode( - node, - isPattern ? "ObjectPattern" : "ObjectExpression", + prop.method = false; + + if (isPattern || refShorthandDefaultPos) { + startPos = this.state.start; + startLoc = this.state.startLoc; + } + + if (!isPattern) { + isGenerator = this.eat(tt.star); + } + + const containsEsc = this.state.containsEsc; + this.parsePropertyName(prop); + + if (!isPattern && !containsEsc && !isGenerator && this.isAsyncProp(prop)) { + isAsync = true; + isGenerator = this.eat(tt.star); + this.parsePropertyName(prop); + } else { + isAsync = false; + } + + this.parseObjPropValue( + prop, + startPos, + startLoc, + isGenerator, + isAsync, + isPattern, + refShorthandDefaultPos, + containsEsc, ); + + return prop; } isGetterOrSetterMethod(prop: N.ObjectMethod, isPattern: boolean): boolean { @@ -1554,10 +1565,16 @@ export default class ExpressionParser extends LValParser { ); } + getGetterSetterExpectedParamCount( + method: N.ObjectMethod | N.ClassMethod, + ): number { + return method.kind === "get" ? 0 : 1; + } + // get methods aren't allowed to have any parameters // set methods must have exactly 1 parameter which is not a rest parameter checkGetterSetterParams(method: N.ObjectMethod | N.ClassMethod): void { - const paramCount = method.kind === "get" ? 0 : 1; + const paramCount = this.getGetterSetterExpectedParamCount(method); const start = method.start; if (method.params.length !== paramCount) { if (method.kind === "get") { @@ -1567,7 +1584,10 @@ export default class ExpressionParser extends LValParser { } } - if (method.kind === "set" && method.params[0].type === "RestElement") { + if ( + method.kind === "set" && + method.params[method.params.length - 1].type === "RestElement" + ) { this.raise( start, "setter function argument must not be a rest parameter", @@ -1591,6 +1611,7 @@ export default class ExpressionParser extends LValParser { isGenerator, isAsync, /* isConstructor */ false, + false, "ObjectMethod", ); } @@ -1604,6 +1625,7 @@ export default class ExpressionParser extends LValParser { /* isGenerator */ false, /* isAsync */ false, /* isConstructor */ false, + false, "ObjectMethod", ); this.checkGetterSetterParams(prop); @@ -1729,32 +1751,28 @@ export default class ExpressionParser extends LValParser { isGenerator: boolean, isAsync: boolean, isConstructor: boolean, + allowDirectSuper: boolean, type: string, + inClassScope: boolean = false, ): T { - const oldInFunc = this.state.inFunction; - const oldInMethod = this.state.inMethod; - const oldInAsync = this.state.inAsync; - const oldInGenerator = this.state.inGenerator; const oldYieldPos = this.state.yieldPos; const oldAwaitPos = this.state.awaitPos; - this.state.inFunction = true; - this.state.inMethod = node.kind || true; - this.state.inAsync = isAsync; - this.state.inGenerator = isGenerator; this.state.yieldPos = 0; this.state.awaitPos = 0; this.initFunction(node, isAsync); node.generator = !!isGenerator; const allowModifiers = isConstructor; // For TypeScript parameter properties + this.scope.enter( + functionFlags(isAsync, node.generator) | + SCOPE_SUPER | + (inClassScope ? SCOPE_CLASS : 0) | + (allowDirectSuper ? SCOPE_DIRECT_SUPER : 0), + ); this.parseFunctionParams((node: any), allowModifiers); this.checkYieldAwaitInDefaultParams(); - this.parseFunctionBodyAndFinish(node, type); + this.parseFunctionBodyAndFinish(node, type, true); - this.state.inFunction = oldInFunc; - this.state.inMethod = oldInMethod; - this.state.inAsync = oldInAsync; - this.state.inGenerator = oldInGenerator; this.state.yieldPos = oldYieldPos; this.state.awaitPos = oldAwaitPos; @@ -1769,17 +1787,12 @@ export default class ExpressionParser extends LValParser { params: ?(N.Expression[]), isAsync: boolean, ): N.ArrowFunctionExpression { + this.scope.enter(functionFlags(isAsync, false) | SCOPE_ARROW); this.initFunction(node, isAsync); - const oldInFunc = this.state.inFunction; - const oldInAsync = this.state.inAsync; - const oldInGenerator = this.state.inGenerator; const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; const oldYieldPos = this.state.yieldPos; const oldAwaitPos = this.state.awaitPos; - this.state.inFunction = true; - this.state.inAsync = isAsync; - this.state.inGenerator = false; this.state.maybeInArrowParameters = false; this.state.yieldPos = 0; this.state.awaitPos = 0; @@ -1787,9 +1800,6 @@ export default class ExpressionParser extends LValParser { if (params) this.setArrowFunctionParameters(node, params); this.parseFunctionBody(node, true); - this.state.inAsync = oldInAsync; - this.state.inGenerator = oldInGenerator; - this.state.inFunction = oldInFunc; this.state.maybeInArrowParameters = oldMaybeInArrowParameters; this.state.yieldPos = oldYieldPos; this.state.awaitPos = oldAwaitPos; @@ -1825,65 +1835,101 @@ export default class ExpressionParser extends LValParser { parseFunctionBodyAndFinish( node: N.BodilessFunctionOrMethodBase, type: string, - allowExpressionBody?: boolean, + isMethod?: boolean = false, ): void { // $FlowIgnore (node is not bodiless if we get here) - this.parseFunctionBody(node, allowExpressionBody); + this.parseFunctionBody(node, false, isMethod); this.finishNode(node, type); } // Parse function body and check parameters. - parseFunctionBody(node: N.Function, allowExpression: ?boolean): void { + parseFunctionBody( + node: N.Function, + allowExpression: ?boolean, + isMethod?: boolean = false, + ): void { const isExpression = allowExpression && !this.match(tt.braceL); + const oldStrict = this.state.strict; + let useStrict = false; const oldInParameters = this.state.inParameters; this.state.inParameters = false; if (isExpression) { node.body = this.parseMaybeAssign(); + this.checkParams(node, false, allowExpression); } else { + const nonSimple = !this.isSimpleParamList(node.params); + if (!oldStrict || nonSimple) { + useStrict = this.strictDirective(this.state.end); + // If this is a strict mode function, verify that argument names + // are not repeated, and it does not try to bind the words `eval` + // or `arguments`. + if (useStrict && nonSimple) { + // This logic is here to align the error location with the estree plugin + const errorPos = + // $FlowIgnore + (node.kind === "method" || node.kind === "constructor") && + // $FlowIgnore + !!node.key + ? node.key.end + : node.start; + this.raise( + errorPos, + "Illegal 'use strict' directive in function with non-simple parameter list", + ); + } + } // Start a new scope with regard to labels // flag (restore them to their old value afterwards). - const oldInFunc = this.state.inFunction; const oldLabels = this.state.labels; - this.state.inFunction = true; this.state.labels = []; - node.body = this.parseBlock(true); - this.state.inFunction = oldInFunc; + if (useStrict) this.state.strict = true; + // Add the params to varDeclaredNames to ensure that an error is thrown + // if a let/const declaration in the function clashes with one of the params. + this.checkParams( + node, + !oldStrict && !useStrict && !allowExpression && !isMethod && !nonSimple, + allowExpression, + ); + node.body = this.parseBlock(true, false); this.state.labels = oldLabels; } + this.scope.exit(); - this.checkFunctionNameAndParams(node, allowExpression); this.state.inParameters = oldInParameters; + // Ensure the function name isn't a forbidden identifier in strict mode, e.g. 'eval' + if (this.state.strict && node.id) { + this.checkLVal(node.id, BIND_OUTSIDE, undefined, "function name"); + } + this.state.strict = oldStrict; + } + + isSimpleParamList( + params: $ReadOnlyArray, + ): boolean { + for (let i = 0, len = params.length; i < len; i++) { + if (params[i].type !== "Identifier") return false; + } + return true; } - checkFunctionNameAndParams( + checkParams( node: N.Function, + allowDuplicates: boolean, + // eslint-disable-next-line no-unused-vars isArrowFunction: ?boolean, ): void { - // If this is a strict mode function, verify that argument names - // are not repeated, and it does not try to bind the words `eval` - // or `arguments`. - const isStrict = this.isStrictBody(node); - // Also check for arrow functions - const checkLVal = this.state.strict || isStrict || isArrowFunction; - - const oldStrict = this.state.strict; - if (isStrict) this.state.strict = isStrict; - - if (checkLVal) { - const nameHash: any = Object.create(null); - if (node.id) { - this.checkLVal(node.id, true, undefined, "function name"); - } - for (const param of node.params) { - if (isStrict && param.type !== "Identifier") { - this.raise(param.start, "Non-simple parameter in strict mode"); - } - this.checkLVal(param, true, nameHash, "function parameter list"); - } + // $FlowIssue + const nameHash: {} = Object.create(null); + for (let i = 0; i < node.params.length; i++) { + this.checkLVal( + node.params[i], + BIND_VAR, + allowDuplicates ? null : nameHash, + "function paramter list", + ); } - this.state.strict = oldStrict; } // Parses a comma-separated list of expressions, and returns them as @@ -1917,6 +1963,7 @@ export default class ExpressionParser extends LValParser { allowEmpty: ?boolean, refShorthandDefaultPos: ?Pos, refNeedsArrowPos: ?Pos, + allowPlaceholder: ?boolean, ): ?N.Expression { let elt; if (allowEmpty && this.match(tt.comma)) { @@ -1929,6 +1976,14 @@ export default class ExpressionParser extends LValParser { spreadNodeStartPos, spreadNodeStartLoc, ); + } else if (this.match(tt.question)) { + this.expectPlugin("partialApplication"); + if (!allowPlaceholder) { + this.raise(this.state.start, "Unexpected argument placeholder"); + } + const node = this.startNode(); + this.next(); + elt = this.finishNode(node, "ArgumentPlaceholder"); } else { elt = this.parseMaybeAssign( false, @@ -1974,8 +2029,7 @@ export default class ExpressionParser extends LValParser { if ( (name === "class" || name === "function") && (this.state.lastTokEnd !== this.state.lastTokStart + 1 || - this.state.input.charCodeAt(this.state.lastTokStart) !== - charCodes.dot) + this.input.charCodeAt(this.state.lastTokStart) !== charCodes.dot) ) { this.state.context.pop(); } @@ -2003,22 +2057,21 @@ export default class ExpressionParser extends LValParser { checkKeywords: boolean, isBinding: boolean, ): void { - const state = this.state; - if (state.inGenerator && word === "yield") { + if (this.scope.inGenerator && word === "yield") { this.raise( startLoc, "Can not use 'yield' as identifier inside a generator", ); } - if (state.inAsync && word === "await") { + if (this.scope.inAsync && word === "await") { this.raise( startLoc, "Can not use 'await' as identifier inside an async function", ); } - if (state.inClassProperty && word === "arguments") { + if (this.state.inClassProperty && word === "arguments") { this.raise( startLoc, "'arguments' is not allowed in class field initializer", @@ -2028,14 +2081,14 @@ export default class ExpressionParser extends LValParser { this.raise(startLoc, `Unexpected keyword '${word}'`); } - const reservedTest = !state.strict + const reservedTest = !this.state.strict ? isReservedWord : isBinding ? isStrictBindReservedWord : isStrictReservedWord; if (reservedTest(word, this.inModule)) { - if (!state.inAsync && word === "await") { + if (!this.scope.inAsync && word === "await") { this.raise( startLoc, "Can not use keyword 'await' outside an async function", diff --git a/packages/babel-parser/src/parser/index.js b/packages/babel-parser/src/parser/index.js index 00617bd8632c..b92ecb8203ad 100644 --- a/packages/babel-parser/src/parser/index.js +++ b/packages/babel-parser/src/parser/index.js @@ -5,6 +5,8 @@ import type { File, JSXOpeningElement } from "../types"; import type { PluginList } from "../plugin-utils"; import { getOptions } from "../options"; import StatementParser from "./statement"; +import { SCOPE_PROGRAM } from "../util/scopeflags"; +import ScopeHandler from "../util/scope"; export type PluginsMap = Map; @@ -20,11 +22,13 @@ export default class Parser extends StatementParser { this.options = options; this.inModule = this.options.sourceType === "module"; + this.scope = new ScopeHandler(this.raise.bind(this), this.inModule); this.plugins = pluginsMap(this.options.plugins); this.filename = options.sourceFilename; } parse(): File { + this.scope.enter(SCOPE_PROGRAM); const file = this.startNode(); const program = this.startNode(); this.nextToken(); diff --git a/packages/babel-parser/src/parser/location.js b/packages/babel-parser/src/parser/location.js index f73724004b06..79db584a4995 100644 --- a/packages/babel-parser/src/parser/location.js +++ b/packages/babel-parser/src/parser/location.js @@ -21,7 +21,7 @@ export default class LocationParser extends CommentsParser { code?: string, } = {}, ): empty { - const loc = getLineInfo(this.state.input, pos); + const loc = getLineInfo(this.input, pos); message += ` (${loc.line}:${loc.column})`; // $FlowIgnore const err: SyntaxError & { pos: number, loc: Position } = new SyntaxError( diff --git a/packages/babel-parser/src/parser/lval.js b/packages/babel-parser/src/parser/lval.js index d200bc7c07e8..6db5558a8ff9 100644 --- a/packages/babel-parser/src/parser/lval.js +++ b/packages/babel-parser/src/parser/lval.js @@ -16,6 +16,7 @@ import type { import type { Pos, Position } from "../util/location"; import { isStrictBindReservedWord } from "../util/identifier"; import { NodeUtils } from "./node"; +import { type BindingTypes, BIND_NONE, BIND_OUTSIDE } from "../util/scopeflags"; export default class LValParser extends NodeUtils { // Forward-declaration: defined in expression.js @@ -129,7 +130,7 @@ export default class LValParser extends NodeUtils { this.raise(prop.key.start, error); } else if (prop.type === "SpreadElement" && !isLast) { - this.raiseRestNotLast(prop.start, "property"); + this.raiseRestNotLast(prop.start); } else { this.toAssignable(prop, isBinding, "object destructuring pattern"); } @@ -167,7 +168,7 @@ export default class LValParser extends NodeUtils { if (elt) { this.toAssignable(elt, isBinding, contextDescription); if (elt.type === "RestElement") { - this.raiseRestNotLast(elt.start, "element"); + this.raiseRestNotLast(elt.start); } } } @@ -220,7 +221,7 @@ export default class LValParser extends NodeUtils { return this.finishNode(node, "SpreadElement"); } - parseRest(): RestElement { + parseRestBinding(): RestElement { const node = this.startNode(); this.next(); node.argument = this.parseBindingAtom(); @@ -267,13 +268,8 @@ export default class LValParser extends NodeUtils { } else if (this.eat(close)) { break; } else if (this.match(tt.ellipsis)) { - elts.push(this.parseAssignableListItemTypes(this.parseRest())); - this.checkCommaAfterRest( - close, - this.state.inFunction && this.state.inParameters - ? "parameter" - : "element", - ); + elts.push(this.parseAssignableListItemTypes(this.parseRestBinding())); + this.checkCommaAfterRest(); this.expect(close); break; } else { @@ -333,7 +329,7 @@ export default class LValParser extends NodeUtils { checkLVal( expr: Expression, - isBinding: ?boolean, + bindingType: ?BindingTypes = BIND_NONE, checkClashes: ?{ [key: string]: boolean }, contextDescription: string, ): void { @@ -345,7 +341,7 @@ export default class LValParser extends NodeUtils { ) { this.raise( expr.start, - `${isBinding ? "Binding" : "Assigning to"} '${ + `${bindingType === BIND_NONE ? "Assigning to" : "Binding"} '${ expr.name }' in strict mode`, ); @@ -366,15 +362,20 @@ export default class LValParser extends NodeUtils { const key = `_${expr.name}`; if (checkClashes[key]) { - this.raise(expr.start, "Argument name clash in strict mode"); + this.raise(expr.start, "Argument name clash"); } else { checkClashes[key] = true; } } + if (bindingType !== BIND_NONE && bindingType !== BIND_OUTSIDE) { + this.scope.declareName(expr.name, bindingType, expr.start); + } break; case "MemberExpression": - if (isBinding) this.raise(expr.start, "Binding member expression"); + if (bindingType !== BIND_NONE) { + this.raise(expr.start, "Binding member expression"); + } break; case "ObjectPattern": @@ -382,7 +383,7 @@ export default class LValParser extends NodeUtils { if (prop.type === "ObjectProperty") prop = prop.value; this.checkLVal( prop, - isBinding, + bindingType, checkClashes, "object destructuring pattern", ); @@ -394,7 +395,7 @@ export default class LValParser extends NodeUtils { if (elem) { this.checkLVal( elem, - isBinding, + bindingType, checkClashes, "array destructuring pattern", ); @@ -405,20 +406,25 @@ export default class LValParser extends NodeUtils { case "AssignmentPattern": this.checkLVal( expr.left, - isBinding, + bindingType, checkClashes, "assignment pattern", ); break; case "RestElement": - this.checkLVal(expr.argument, isBinding, checkClashes, "rest element"); + this.checkLVal( + expr.argument, + bindingType, + checkClashes, + "rest element", + ); break; case "ParenthesizedExpression": this.checkLVal( expr.expression, - isBinding, + bindingType, checkClashes, "parenthesized expression", ); @@ -426,9 +432,9 @@ export default class LValParser extends NodeUtils { default: { const message = - (isBinding - ? /* istanbul ignore next */ "Binding invalid" - : "Invalid") + + (bindingType === BIND_NONE + ? "Invalid" + : /* istanbul ignore next */ "Binding invalid") + " left-hand side" + (contextDescription ? " in " + contextDescription @@ -447,27 +453,19 @@ export default class LValParser extends NodeUtils { } } - checkCommaAfterRest(close: TokenType, kind: string): void { + checkCommaAfterRest(): void { if (this.match(tt.comma)) { - if (this.lookahead().type === close) { - this.raiseCommaAfterRest(this.state.start, kind); - } else { - this.raiseRestNotLast(this.state.start, kind); - } + this.raiseRestNotLast(this.state.start); } } - checkCommaAfterRestFromSpread(kind: string): void { + checkCommaAfterRestFromSpread(): void { if (this.state.commaAfterSpreadAt > -1) { - this.raiseCommaAfterRest(this.state.commaAfterSpreadAt, kind); + this.raiseRestNotLast(this.state.commaAfterSpreadAt); } } - raiseCommaAfterRest(pos: number, kind: string) { - this.raise(pos, `A trailing comma is not permitted after the rest ${kind}`); - } - - raiseRestNotLast(pos: number, kind: string) { - this.raise(pos, `The rest ${kind} must be the last ${kind}`); + raiseRestNotLast(pos: number) { + this.raise(pos, `Rest element must be last element`); } } diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 35a291d08b9e..d89aa6b11567 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -10,6 +10,17 @@ import { } from "../util/identifier"; import { lineBreak, skipWhiteSpace } from "../util/whitespace"; import * as charCodes from "charcodes"; +import { + BIND_SIMPLE_CATCH, + BIND_LEXICAL, + BIND_VAR, + BIND_FUNCTION, + functionFlags, + SCOPE_CLASS, + SCOPE_OTHER, + SCOPE_SIMPLE_CATCH, + SCOPE_SUPER, +} from "../util/scopeflags"; // Reused empty array added for node fields that are always empty. @@ -18,6 +29,11 @@ const empty = []; const loopLabel = { kind: "loop" }, switchLabel = { kind: "switch" }; +const FUNC_NO_FLAGS = 0b000, + FUNC_STATEMENT = 0b001, + FUNC_HANGING_STATEMENT = 0b010, + FUNC_NULLABLE_ID = 0b100; + export default class StatementParser extends ExpressionParser { // ### Statement parsing @@ -33,6 +49,14 @@ export default class StatementParser extends ExpressionParser { this.parseBlockBody(program, true, true, tt.eof); + if (this.inModule && this.scope.undefinedExports.size > 0) { + for (const [name] of Array.from(this.scope.undefinedExports)) { + const pos = this.scope.undefinedExports.get(name); + // $FlowIssue + this.raise(pos, `Export '${name}' is not defined`); + } + } + file.program = this.finishNode(program, "Program"); file.comments = this.state.comments; @@ -49,7 +73,7 @@ export default class StatementParser extends ExpressionParser { const directiveLiteral = this.startNodeAt(expr.start, expr.loc.start); const directive = this.startNodeAt(stmt.start, stmt.loc.start); - const raw = this.state.input.slice(expr.start, expr.end); + const raw = this.input.slice(expr.start, expr.end); const val = (directiveLiteral.value = raw.slice(1, -1)); // remove quotes this.addExtra(directiveLiteral, "raw", raw); @@ -81,10 +105,10 @@ export default class StatementParser extends ExpressionParser { return false; } skipWhiteSpace.lastIndex = this.state.pos; - const skip = skipWhiteSpace.exec(this.state.input); + const skip = skipWhiteSpace.exec(this.input); // $FlowIgnore const next = this.state.pos + skip[0].length; - const nextCh = this.state.input.charCodeAt(next); + const nextCh = this.input.charCodeAt(next); // For ambiguous cases, determine if a LexicalDeclaration (or only a // Statement) is allowed here. If context is not empty then only a Statement // is allowed. However, `let [` is an explicit negative lookahead for @@ -96,10 +120,10 @@ export default class StatementParser extends ExpressionParser { if (isIdentifierStart(nextCh)) { let pos = next + 1; - while (isIdentifierChar(this.state.input.charCodeAt(pos))) { + while (isIdentifierChar(this.input.charCodeAt(pos))) { ++pos; } - const ident = this.state.input.slice(next, pos); + const ident = this.input.slice(next, pos); if (!keywordRelationalOperator.test(ident)) return true; } return false; @@ -144,26 +168,24 @@ export default class StatementParser extends ExpressionParser { return this.parseDoStatement(node); case tt._for: return this.parseForStatement(node); - case tt._function: { + case tt._function: if (this.lookahead().type === tt.dot) break; - if ( - context && - (this.state.strict || (context !== "if" && context !== "label")) - ) { - this.raise( - this.state.start, - "Function declaration not allowed in this context", - ); - } - const result = this.parseFunctionStatement(node); - - // TODO: Remove this once we have proper scope tracking in place. - if (context && result.generator) { - this.unexpected(node.start); + if (context) { + if (this.state.strict) { + this.raise( + this.state.start, + "In strict mode code, functions can only be declared at top level or inside a block", + ); + } else if (context !== "if" && context !== "label") { + this.raise( + this.state.start, + "In non-strict mode code, functions can only be declared at top level, " + + "inside a block, or as the body of an if statement", + ); + } } + return this.parseFunctionStatement(node, false, !context); - return result; - } case tt._class: if (context) this.unexpected(); return this.parseClass(node, true); @@ -182,7 +204,12 @@ export default class StatementParser extends ExpressionParser { case tt._const: case tt._var: kind = kind || this.state.value; - if (context && kind !== "var") this.unexpected(); + if (context && kind !== "var") { + this.unexpected( + this.state.start, + "Lexical declaration cannot appear in a single-statement context", + ); + } return this.parseVarStatement(node, kind); case tt._while: @@ -237,24 +264,19 @@ export default class StatementParser extends ExpressionParser { return result; } - case tt.name: - if (this.isContextual("async")) { - // peek ahead and see if next token is a function - const state = this.state.clone(); - this.next(); - if (this.match(tt._function) && !this.canInsertSemicolon()) { - if (context) { - this.raise( - this.state.lastTokStart, - "Function declaration not allowed in this context", - ); - } - this.next(); - return this.parseFunction(node, true, false, true); - } else { - this.state = state; + + default: { + if (this.isAsyncFunction()) { + if (context) { + this.unexpected( + null, + "Async functions can only be declared at the top level or inside a block", + ); } + this.next(); + return this.parseFunctionStatement(node, true, !context); } + } } // If the statement does not start with a statement keyword or a @@ -394,15 +416,24 @@ export default class StatementParser extends ExpressionParser { if (this.isLineTerminator()) { node.label = null; - } else if (!this.match(tt.name)) { - this.unexpected(); } else { node.label = this.parseIdentifier(); this.semicolon(); } - // Verify that there is an actual destination to break or - // continue to. + this.verifyBreakContinue(node, keyword); + + return this.finishNode( + node, + isBreak ? "BreakStatement" : "ContinueStatement", + ); + } + + verifyBreakContinue( + node: N.BreakStatement | N.ContinueStatement, + keyword: string, + ) { + const isBreak = keyword === "break"; let i; for (i = 0; i < this.state.labels.length; ++i) { const lab = this.state.labels[i]; @@ -414,10 +445,6 @@ export default class StatementParser extends ExpressionParser { if (i === this.state.labels.length) { this.raise(node.start, "Unsyntactic " + keyword); } - return this.finishNode( - node, - isBreak ? "BreakStatement" : "ContinueStatement", - ); } parseDebuggerStatement(node: N.DebuggerStatement): N.DebuggerStatement { @@ -468,12 +495,13 @@ export default class StatementParser extends ExpressionParser { let awaitAt = -1; if ( - (this.state.inAsync || - (!this.state.inFunction && this.options.allowAwaitOutsideFunction)) && + (this.scope.inAsync || + (!this.scope.inFunction && this.options.allowAwaitOutsideFunction)) && this.eatContextual("await") ) { awaitAt = this.state.lastTokStart; } + this.scope.enter(SCOPE_OTHER); this.expect(tt.parenL); if (this.match(tt.semi)) { @@ -531,9 +559,17 @@ export default class StatementParser extends ExpressionParser { return this.parseFor(node, init); } - parseFunctionStatement(node: N.FunctionDeclaration): N.FunctionDeclaration { + parseFunctionStatement( + node: N.FunctionDeclaration, + isAsync?: boolean, + declarationPosition?: boolean, + ): N.FunctionDeclaration { this.next(); - return this.parseFunction(node, true); + return this.parseFunction( + node, + FUNC_STATEMENT | (declarationPosition ? 0 : FUNC_HANGING_STATEMENT), + isAsync, + ); } parseIfStatement(node: N.IfStatement): N.IfStatement { @@ -545,7 +581,7 @@ export default class StatementParser extends ExpressionParser { } parseReturnStatement(node: N.ReturnStatement): N.ReturnStatement { - if (!this.state.inFunction && !this.options.allowReturnOutsideFunction) { + if (!this.scope.inFunction && !this.options.allowReturnOutsideFunction) { this.raise(this.state.start, "'return' outside of function"); } @@ -571,6 +607,7 @@ export default class StatementParser extends ExpressionParser { const cases = (node.cases = []); this.expect(tt.braceL); this.state.labels.push(switchLabel); + this.scope.enter(SCOPE_OTHER); // Statements under must be grouped (by label) in SwitchCase // nodes. `cur` is used to keep the node that we are currently @@ -602,6 +639,7 @@ export default class StatementParser extends ExpressionParser { } } } + this.scope.exit(); if (cur) this.finishNode(cur, "SwitchCase"); this.next(); // Closing brace this.state.labels.pop(); @@ -611,9 +649,7 @@ export default class StatementParser extends ExpressionParser { parseThrowStatement(node: N.ThrowStatement): N.ThrowStatement { this.next(); if ( - lineBreak.test( - this.state.input.slice(this.state.lastTokEnd, this.state.start), - ) + lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start)) ) { this.raise(this.state.lastTokEnd, "Illegal newline after throw"); } @@ -634,11 +670,18 @@ export default class StatementParser extends ExpressionParser { if (this.match(tt.parenL)) { this.expect(tt.parenL); clause.param = this.parseBindingAtom(); - const clashes: any = Object.create(null); - this.checkLVal(clause.param, true, clashes, "catch clause"); + const simple = clause.param.type === "Identifier"; + this.scope.enter(simple ? SCOPE_SIMPLE_CATCH : 0); + this.checkLVal( + clause.param, + simple ? BIND_SIMPLE_CATCH : BIND_LEXICAL, + null, + "catch clause", + ); this.expect(tt.parenR); } else { clause.param = null; + this.scope.enter(SCOPE_OTHER); } clause.body = @@ -648,8 +691,9 @@ export default class StatementParser extends ExpressionParser { // outside of the function body. this.withTopicForbiddingContext(() => // Parse the catch clause's body. - this.parseBlock(false), + this.parseBlock(false, false), ); + this.scope.exit(); node.handler = this.finishNode(clause, "CatchClause"); } @@ -766,7 +810,7 @@ export default class StatementParser extends ExpressionParser { parseExpressionStatement( node: N.ExpressionStatement, expr: N.Expression, - ): N.ExpressionStatement { + ): N.Statement { node.expression = expr; this.semicolon(); return this.finishNode(node, "ExpressionStatement"); @@ -776,10 +820,19 @@ export default class StatementParser extends ExpressionParser { // strict"` declarations when `allowStrict` is true (used for // function bodies). - parseBlock(allowDirectives?: boolean): N.BlockStatement { + parseBlock( + allowDirectives?: boolean = false, + createNewLexicalScope?: boolean = true, + ): N.BlockStatement { const node = this.startNode(); this.expect(tt.braceL); + if (createNewLexicalScope) { + this.scope.enter(SCOPE_OTHER); + } this.parseBlockBody(node, allowDirectives, false, tt.braceR); + if (createNewLexicalScope) { + this.scope.exit(); + } return this.finishNode(node, "BlockStatement"); } @@ -874,6 +927,7 @@ export default class StatementParser extends ExpressionParser { this.parseStatement("for"), ); + this.scope.exit(); this.state.labels.pop(); return this.finishNode(node, "ForStatement"); @@ -909,6 +963,7 @@ export default class StatementParser extends ExpressionParser { this.parseStatement("for"), ); + this.scope.exit(); this.state.labels.pop(); return this.finishNode(node, type); @@ -961,7 +1016,12 @@ export default class StatementParser extends ExpressionParser { this.unexpected(null, "let is disallowed as a lexically bound name"); } decl.id = this.parseBindingAtom(); - this.checkLVal(decl.id, true, undefined, "variable declaration"); + this.checkLVal( + decl.id, + kind === "var" ? BIND_VAR : BIND_LEXICAL, + undefined, + "variable declaration", + ); } // Parse a function declaration or literal (depending on the @@ -969,51 +1029,53 @@ export default class StatementParser extends ExpressionParser { parseFunction( node: T, - isStatement: boolean, - allowExpressionBody?: boolean = false, + statement?: number = FUNC_NO_FLAGS, isAsync?: boolean = false, - optionalId?: boolean = false, ): T { - const oldInFunc = this.state.inFunction; - const oldInMethod = this.state.inMethod; - const oldInAsync = this.state.inAsync; - const oldInGenerator = this.state.inGenerator; - const oldInClassProperty = this.state.inClassProperty; - const oldYieldPos = this.state.yieldPos; - const oldAwaitPos = this.state.awaitPos; - this.state.inFunction = true; - this.state.inMethod = false; - this.state.inClassProperty = false; - this.state.yieldPos = 0; - this.state.awaitPos = 0; + const isStatement = statement & FUNC_STATEMENT; + const isHangingStatement = statement & FUNC_HANGING_STATEMENT; + const requireId = !!isStatement && !(statement & FUNC_NULLABLE_ID); this.initFunction(node, isAsync); + if (this.match(tt.star) && isHangingStatement) { + this.unexpected( + this.state.start, + "Generators can only be declared at the top level or inside a block", + ); + } node.generator = this.eat(tt.star); - if (isStatement && !optionalId && !this.match(tt.name)) { - this.unexpected(); + if (isStatement) { + node.id = this.parseFunctionId(requireId); + if (node.id && !isHangingStatement) { + // If it is a regular function declaration in sloppy mode, then it is + // subject to Annex B semantics (BIND_FUNCTION). Otherwise, the binding + // mode depends on properties of the current scope (see + // treatFunctionsAsVar). + this.checkLVal( + node.id, + this.state.strict || node.generator || node.async + ? this.scope.treatFunctionsAsVar + ? BIND_VAR + : BIND_LEXICAL + : BIND_FUNCTION, + null, + "function name", + ); + } } - // When parsing function expression, the binding identifier is parsed - // according to the rules inside the function. - // e.g. (function* yield() {}) is invalid because "yield" is disallowed in - // generators. - // This isn't the case with function declarations: function* yield() {} is - // valid because yield is parsed as if it was outside the generator. - // Therefore, this.state.inGenerator is set before or after parsing the - // function id according to the "isStatement" parameter. - // The same applies to await & async functions. + const oldInClassProperty = this.state.inClassProperty; + const oldYieldPos = this.state.yieldPos; + const oldAwaitPos = this.state.awaitPos; + this.state.inClassProperty = false; + this.state.yieldPos = 0; + this.state.awaitPos = 0; + this.scope.enter(functionFlags(node.async, node.generator)); + if (!isStatement) { - this.state.inAsync = isAsync; - this.state.inGenerator = node.generator; - } - if (this.match(tt.name)) { - node.id = this.parseIdentifier(); - } - if (isStatement) { - this.state.inAsync = isAsync; - this.state.inGenerator = node.generator; + node.id = this.parseFunctionId(); } this.parseFunctionParams(node); @@ -1026,14 +1088,9 @@ export default class StatementParser extends ExpressionParser { this.parseFunctionBodyAndFinish( node, isStatement ? "FunctionDeclaration" : "FunctionExpression", - allowExpressionBody, ); }); - this.state.inFunction = oldInFunc; - this.state.inMethod = oldInMethod; - this.state.inAsync = oldInAsync; - this.state.inGenerator = oldInGenerator; this.state.inClassProperty = oldInClassProperty; this.state.yieldPos = oldYieldPos; this.state.awaitPos = oldAwaitPos; @@ -1041,6 +1098,10 @@ export default class StatementParser extends ExpressionParser { return node; } + parseFunctionId(requireId?: boolean): ?N.Identifier { + return requireId || this.match(tt.name) ? this.parseIdentifier() : null; + } + parseFunctionParams(node: N.Function, allowModifiers?: boolean): void { const oldInParameters = this.state.inParameters; this.state.inParameters = true; @@ -1073,7 +1134,7 @@ export default class StatementParser extends ExpressionParser { this.parseClassId(node, isStatement, optionalId); this.parseClassSuper(node); - this.parseClassBody(node); + node.body = this.parseClassBody(!!node.superClass); this.state.strict = oldStrict; @@ -1100,13 +1161,12 @@ export default class StatementParser extends ExpressionParser { ); } - parseClassBody(node: N.Class): void { + parseClassBody(constructorAllowsSuper: boolean): N.ClassBody { this.state.classLevel++; const state = { hadConstructor: false }; let decorators: N.Decorator[] = []; const classBody: N.ClassBody = this.startNode(); - classBody.body = []; this.expect(tt.braceL); @@ -1140,7 +1200,7 @@ export default class StatementParser extends ExpressionParser { decorators = []; } - this.parseClassMember(classBody, member, state); + this.parseClassMember(classBody, member, state, constructorAllowsSuper); if ( member.kind === "constructor" && @@ -1162,15 +1222,16 @@ export default class StatementParser extends ExpressionParser { ); } - node.body = this.finishNode(classBody, "ClassBody"); - this.state.classLevel--; + + return this.finishNode(classBody, "ClassBody"); } parseClassMember( classBody: N.ClassBody, member: N.ClassMember, state: { hadConstructor: boolean }, + constructorAllowsSuper: boolean, ): void { let isStatic = false; const containsEsc = this.state.containsEsc; @@ -1192,6 +1253,7 @@ export default class StatementParser extends ExpressionParser { false, false, /* isConstructor */ false, + false, ); return; } else if (this.isClassProperty()) { @@ -1211,7 +1273,13 @@ export default class StatementParser extends ExpressionParser { isStatic = true; } - this.parseClassMemberWithIsStatic(classBody, member, state, isStatic); + this.parseClassMemberWithIsStatic( + classBody, + member, + state, + isStatic, + constructorAllowsSuper, + ); } parseClassMemberWithIsStatic( @@ -1219,6 +1287,7 @@ export default class StatementParser extends ExpressionParser { member: N.ClassMember, state: { hadConstructor: boolean }, isStatic: boolean, + constructorAllowsSuper: boolean, ) { const publicMethod: $FlowSubtype = member; const privateMethod: $FlowSubtype = member; @@ -1251,11 +1320,13 @@ export default class StatementParser extends ExpressionParser { true, false, /* isConstructor */ false, + false, ); return; } + const containsEsc = this.state.containsEsc; const key = this.parseClassPropertyName(member); const isPrivate = key.type === "PrivateName"; // Check the key is not a computed expression or string literal. @@ -1273,7 +1344,7 @@ export default class StatementParser extends ExpressionParser { // a normal method const isConstructor = this.isNonstaticConstructor(publicMethod); - + let allowsDirectSuper = false; if (isConstructor) { publicMethod.kind = "constructor"; @@ -1289,6 +1360,7 @@ export default class StatementParser extends ExpressionParser { this.raise(key.start, "Duplicate constructor in the same class"); } state.hadConstructor = true; + allowsDirectSuper = constructorAllowsSuper; } this.pushClassMethod( @@ -1297,6 +1369,7 @@ export default class StatementParser extends ExpressionParser { false, false, isConstructor, + allowsDirectSuper, ); } else if (this.isClassProperty()) { if (isPrivate) { @@ -1304,7 +1377,12 @@ export default class StatementParser extends ExpressionParser { } else { this.pushClassProperty(classBody, publicProp); } - } else if (isSimple && key.name === "async" && !this.isLineTerminator()) { + } else if ( + isSimple && + key.name === "async" && + !containsEsc && + !this.isLineTerminator() + ) { // an async method const isGenerator = this.eat(tt.star); @@ -1334,11 +1412,13 @@ export default class StatementParser extends ExpressionParser { isGenerator, true, /* isConstructor */ false, + false, ); } } else if ( isSimple && (key.name === "get" || key.name === "set") && + !containsEsc && !(this.match(tt.star) && this.isLineTerminator()) ) { // `get\n*` is an uninitialized property named 'get' followed by a generator. @@ -1363,6 +1443,7 @@ export default class StatementParser extends ExpressionParser { false, false, /* isConstructor */ false, + false, ); } @@ -1429,6 +1510,7 @@ export default class StatementParser extends ExpressionParser { isGenerator: boolean, isAsync: boolean, isConstructor: boolean, + allowsDirectSuper: boolean, ): void { classBody.body.push( this.parseMethod( @@ -1436,7 +1518,9 @@ export default class StatementParser extends ExpressionParser { isGenerator, isAsync, isConstructor, + allowsDirectSuper, "ClassMethod", + true, ), ); } @@ -1454,7 +1538,9 @@ export default class StatementParser extends ExpressionParser { isGenerator, isAsync, /* isConstructor */ false, + false, "ClassPrivateMethod", + true, ), ); } @@ -1473,13 +1559,16 @@ export default class StatementParser extends ExpressionParser { parseClassPrivateProperty( node: N.ClassPrivateProperty, ): N.ClassPrivateProperty { - const oldInMethod = this.state.inMethod; - this.state.inMethod = false; this.state.inClassProperty = true; + + this.scope.enter(SCOPE_CLASS | SCOPE_SUPER); + node.value = this.eat(tt.eq) ? this.parseMaybeAssign() : null; this.semicolon(); this.state.inClassProperty = false; - this.state.inMethod = oldInMethod; + + this.scope.exit(); + return this.finishNode(node, "ClassPrivateProperty"); } @@ -1488,10 +1577,10 @@ export default class StatementParser extends ExpressionParser { this.expectPlugin("classProperties"); } - const oldInMethod = this.state.inMethod; - this.state.inMethod = false; this.state.inClassProperty = true; + this.scope.enter(SCOPE_CLASS | SCOPE_SUPER); + if (this.match(tt.eq)) { this.expectPlugin("classProperties"); this.next(); @@ -1501,7 +1590,8 @@ export default class StatementParser extends ExpressionParser { } this.semicolon(); this.state.inClassProperty = false; - this.state.inMethod = oldInMethod; + + this.scope.exit(); return this.finishNode(node, "ClassProperty"); } @@ -1513,6 +1603,9 @@ export default class StatementParser extends ExpressionParser { ): void { if (this.match(tt.name)) { node.id = this.parseIdentifier(); + if (isStatement) { + this.checkLVal(node.id, BIND_LEXICAL, undefined, "class name"); + } } else { if (optionalId || !isStatement) { node.id = null; @@ -1563,7 +1656,7 @@ export default class StatementParser extends ExpressionParser { } if (isFromRequired || hasSpecifiers || hasDeclaration) { - this.checkExport(node, true); + this.checkExport(node, true, false, !!node.source); return this.finishNode(node, "ExportNamedDeclaration"); } @@ -1649,22 +1742,23 @@ export default class StatementParser extends ExpressionParser { return false; } - isAsyncFunction() { + isAsyncFunction(): boolean { if (!this.isContextual("async")) return false; - const { input, pos, length } = this.state; + const { pos } = this.state; skipWhiteSpace.lastIndex = pos; - const skip = skipWhiteSpace.exec(input); + const skip = skipWhiteSpace.exec(this.input); if (!skip || !skip.length) return false; const next = pos + skip[0].length; return ( - !lineBreak.test(input.slice(pos, next)) && - input.slice(next, next + 8) === "function" && - (next + 8 === length || !isIdentifierChar(input.charCodeAt(next + 8))) + !lineBreak.test(this.input.slice(pos, next)) && + this.input.slice(next, next + 8) === "function" && + (next + 8 === this.length || + !isIdentifierChar(this.input.charCodeAt(next + 8))) ); } @@ -1673,13 +1767,17 @@ export default class StatementParser extends ExpressionParser { const isAsync = this.isAsyncFunction(); - if (this.eat(tt._function) || isAsync) { + if (this.match(tt._function) || isAsync) { + this.next(); if (isAsync) { - this.eatContextual("async"); - this.expect(tt._function); + this.next(); } - return this.parseFunction(expr, true, false, isAsync, true); + return this.parseFunction( + expr, + FUNC_STATEMENT | FUNC_NULLABLE_ID, + isAsync, + ); } else if (this.match(tt._class)) { return this.parseClass(expr, true, true); } else if (this.match(tt.at)) { @@ -1773,8 +1871,9 @@ export default class StatementParser extends ExpressionParser { checkExport( node: N.ExportNamedDeclaration, - checkNames: ?boolean, + checkNames?: boolean, isDefault?: boolean, + isFrom?: boolean, ): void { if (checkNames) { // Check for duplicate exports @@ -1785,6 +1884,19 @@ export default class StatementParser extends ExpressionParser { // Named exports for (const specifier of node.specifiers) { this.checkDuplicateExports(specifier, specifier.exported.name); + // $FlowIgnore + if (!isFrom && specifier.local) { + // check for keywords used as local names + this.checkReservedWord( + specifier.local.name, + specifier.local.start, + true, + false, + ); + // check if export is defined + // $FlowIgnore + this.scope.checkLocalExport(specifier.local); + } } } else if (node.declaration) { // Exported declarations @@ -1868,7 +1980,6 @@ export default class StatementParser extends ExpressionParser { parseExportSpecifiers(): Array { const nodes = []; let first = true; - let needsFrom; // export { x, y as z } [from '...'] this.expect(tt.braceL); @@ -1881,22 +1992,14 @@ export default class StatementParser extends ExpressionParser { if (this.eat(tt.braceR)) break; } - const isDefault = this.match(tt._default); - if (isDefault && !needsFrom) needsFrom = true; - const node = this.startNode(); - node.local = this.parseIdentifier(isDefault); + node.local = this.parseIdentifier(true); node.exported = this.eatContextual("as") ? this.parseIdentifier(true) : node.local.__clone(); nodes.push(this.finishNode(node, "ExportSpecifier")); } - // https://github.com/ember-cli/ember-cli/pull/3739 - if (needsFrom && !this.isContextual("from")) { - this.unexpected(); - } - return nodes; } @@ -1934,7 +2037,12 @@ export default class StatementParser extends ExpressionParser { contextDescription: string, ): void { specifier.local = this.parseIdentifier(); - this.checkLVal(specifier.local, true, undefined, contextDescription); + this.checkLVal( + specifier.local, + BIND_LEXICAL, + undefined, + contextDescription, + ); node.specifiers.push(this.finishNode(specifier, type)); } @@ -2007,7 +2115,12 @@ export default class StatementParser extends ExpressionParser { ); specifier.local = specifier.imported.__clone(); } - this.checkLVal(specifier.local, true, undefined, "import specifier"); + this.checkLVal( + specifier.local, + BIND_LEXICAL, + undefined, + "import specifier", + ); node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); } } diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index 1ea8aeaf8f1c..bd6139a2480a 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -3,7 +3,9 @@ import { types as tt, type TokenType } from "../tokenizer/types"; import Tokenizer from "../tokenizer"; import type { Node } from "../types"; -import { lineBreak } from "../util/whitespace"; +import { lineBreak, skipWhiteSpace } from "../util/whitespace"; + +const literal = /^('|")((?:\\?.)*?)\1/; // ## Parser utilities @@ -87,7 +89,7 @@ export default class UtilParser extends Tokenizer { hasPrecedingLineBreak(): boolean { return lineBreak.test( - this.state.input.slice(this.state.lastTokEnd, this.state.start), + this.input.slice(this.state.lastTokEnd, this.state.start), ); } @@ -111,6 +113,13 @@ export default class UtilParser extends Tokenizer { this.eat(type) || this.unexpected(pos, type); } + // Throws if the current token and the prev one are separated by a space. + assertNoSpace(message: string = "Unexpected space."): void { + if (this.state.start > this.state.lastTokEnd) { + this.raise(this.state.lastTokEnd, message); + } + } + // Raise an unexpected token error. Can take the expected token type // instead of a message string. @@ -165,4 +174,27 @@ export default class UtilParser extends Tokenizer { ); } } + + strictDirective(start: number): boolean { + for (;;) { + // Try to find string literal. + skipWhiteSpace.lastIndex = start; + // $FlowIgnore + start += skipWhiteSpace.exec(this.input)[0].length; + const match = literal.exec(this.input.slice(start)); + if (!match) break; + if (match[2] === "use strict") return true; + start += match[0].length; + + // Skip semicolon, if any. + skipWhiteSpace.lastIndex = start; + // $FlowIgnore + start += skipWhiteSpace.exec(this.input)[0].length; + if (this.input[start] === ";") { + start++; + } + } + + return false; + } } diff --git a/packages/babel-parser/src/plugin-utils.js b/packages/babel-parser/src/plugin-utils.js index 317bdff32352..c1cb6a6b7e92 100644 --- a/packages/babel-parser/src/plugin-utils.js +++ b/packages/babel-parser/src/plugin-utils.js @@ -88,12 +88,17 @@ import estree from "./plugins/estree"; import flow from "./plugins/flow"; import jsx from "./plugins/jsx"; import typescript from "./plugins/typescript"; +import placeholders from "./plugins/placeholders"; -// NOTE: estree must load first; flow and typescript must load last. -export const mixinPluginNames = ["estree", "jsx", "flow", "typescript"]; +// NOTE: order is important. estree must come first; placeholders must come last. export const mixinPlugins: { [name: string]: MixinPlugin } = { estree, jsx, flow, typescript, + placeholders, }; + +export const mixinPluginNames: $ReadOnlyArray = Object.keys( + mixinPlugins, +); diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index ae859107b066..38e7e2e01cdd 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -4,6 +4,7 @@ import { types as tt, TokenType } from "../tokenizer/types"; import type Parser from "../parser"; import * as N from "../types"; import type { Pos, Position } from "../util/location"; +import { type BindingTypes, BIND_NONE } from "../util/scopeflags"; function isSimpleProperty(node: N.Node): boolean { return ( @@ -104,7 +105,7 @@ export default (superClass: Class): Class => checkLVal( expr: N.Expression, - isBinding: ?boolean, + bindingType: ?BindingTypes = BIND_NONE, checkClashes: ?{ [key: string]: boolean }, contextDescription: string, ): void { @@ -113,28 +114,36 @@ export default (superClass: Class): Class => expr.properties.forEach(prop => { this.checkLVal( prop.type === "Property" ? prop.value : prop, - isBinding, + bindingType, checkClashes, "object destructuring pattern", ); }); break; default: - super.checkLVal(expr, isBinding, checkClashes, contextDescription); + super.checkLVal(expr, bindingType, checkClashes, contextDescription); } } checkPropClash( - prop: N.ObjectMember, + prop: N.ObjectMember | N.SpreadElement, propHash: { [key: string]: boolean }, ): void { - if (prop.computed || !isSimpleProperty(prop)) return; + if ( + prop.type === "SpreadElement" || + prop.computed || + prop.method || + // $FlowIgnore + prop.shorthand + ) { + return; + } const key = prop.key; // It is either an Identifier or a String/NumericLiteral const name = key.type === "Identifier" ? key.name : String(key.value); - if (name === "__proto__") { + if (name === "__proto__" && prop.kind === "init") { if (propHash.proto) { this.raise(key.start, "Redefinition of __proto__ property"); } @@ -203,13 +212,16 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isConstructor: boolean, + allowsDirectSuper: boolean, ): void { this.parseMethod( method, isGenerator, isAsync, isConstructor, + allowsDirectSuper, "MethodDefinition", + true, ); if (method.typeParameters) { // $FlowIgnore @@ -255,8 +267,12 @@ export default (superClass: Class): Class => return node; } - parseFunctionBody(node: N.Function, allowExpression: ?boolean): void { - super.parseFunctionBody(node, allowExpression); + parseFunctionBody( + node: N.Function, + allowExpression: ?boolean, + isMethod?: boolean = false, + ): void { + super.parseFunctionBody(node, allowExpression, isMethod); node.expression = node.body.type !== "BlockStatement"; } @@ -265,7 +281,9 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isConstructor: boolean, + allowDirectSuper: boolean, type: string, + inClassScope: boolean = false, ): T { let funcNode = this.startNode(); funcNode.kind = node.kind; // provide kind, so super method correctly sets state @@ -274,7 +292,9 @@ export default (superClass: Class): Class => isGenerator, isAsync, isConstructor, + allowDirectSuper, "FunctionExpression", + inClassScope, ); delete funcNode.kind; // $FlowIgnore diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 1effe1725c0e..dc2de5036bcc 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -9,6 +9,12 @@ import type State from "../tokenizer/state"; import { types as tc } from "../tokenizer/context"; import * as charCodes from "charcodes"; import { isIteratorStart } from "../util/identifier"; +import { + type BindingTypes, + BIND_NONE, + BIND_LEXICAL, + SCOPE_OTHER, +} from "../util/scopeflags"; const reservedTypes = [ "any", @@ -257,6 +263,8 @@ export default (superClass: Class): Class => flowParseDeclareModule(node: N.FlowDeclareModule): N.FlowDeclareModule { this.next(); + this.scope.enter(SCOPE_OTHER); + if (this.match(tt.string)) { node.id = this.parseExprAtom(); } else { @@ -290,6 +298,9 @@ export default (superClass: Class): Class => body.push(bodyNode); } + + this.scope.exit(); + this.expect(tt.braceR); this.finishNode(bodyNode, "BlockStatement"); @@ -518,6 +529,7 @@ export default (superClass: Class): Class => flowParseTypeAlias(node: N.FlowTypeAlias): N.FlowTypeAlias { node.id = this.flowParseRestrictedIdentifier(); + this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start); if (this.isRelational("<")) { node.typeParameters = this.flowParseTypeParameterDeclaration(); @@ -537,6 +549,7 @@ export default (superClass: Class): Class => ): N.FlowOpaqueType { this.expectContextual("type"); node.id = this.flowParseRestrictedIdentifier(/*liberal*/ true); + this.scope.declareName(node.id.name, BIND_LEXICAL, node.id.start); if (this.isRelational("<")) { node.typeParameters = this.flowParseTypeParameterDeclaration(); @@ -1124,7 +1137,7 @@ export default (superClass: Class): Class => node.types = []; this.expect(tt.bracketL); // We allow trailing commas - while (this.state.pos < this.state.length && !this.match(tt.bracketR)) { + while (this.state.pos < this.length && !this.match(tt.bracketR)) { node.types.push(this.flowParseType()); if (this.match(tt.bracketR)) break; this.expect(tt.comma); @@ -1531,23 +1544,26 @@ export default (superClass: Class): Class => // Overrides // ================================== - parseFunctionBody(node: N.Function, allowExpressionBody: ?boolean): void { + parseFunctionBody( + node: N.Function, + allowExpressionBody: ?boolean, + isMethod?: boolean = false, + ): void { if (allowExpressionBody) { return this.forwardNoArrowParamsConversionAt(node, () => - super.parseFunctionBody(node, true), + super.parseFunctionBody(node, true, isMethod), ); } - return super.parseFunctionBody(node, false); + return super.parseFunctionBody(node, false, isMethod); } parseFunctionBodyAndFinish( node: N.BodilessFunctionOrMethodBase, type: string, - allowExpressionBody?: boolean, + isMethod?: boolean = false, ): void { - // For arrow functions, `parseArrow` handles the return type itself. - if (!allowExpressionBody && this.match(tt.colon)) { + if (this.match(tt.colon)) { const typeNode = this.startNode(); [ @@ -1562,7 +1578,7 @@ export default (superClass: Class): Class => : null; } - super.parseFunctionBodyAndFinish(node, type, allowExpressionBody); + super.parseFunctionBodyAndFinish(node, type, isMethod); } // interfaces @@ -1761,7 +1777,7 @@ export default (superClass: Class): Class => "arrow function parameters", ); // Use super's method to force the parameters to be checked - super.checkFunctionNameAndParams(node, true); + super.checkParams(node, false, true); } else { arrows.push(node); } @@ -1922,7 +1938,7 @@ export default (superClass: Class): Class => // ensure that inside flow types, we bypass the jsx parser plugin getTokenFromCode(code: number): void { - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if (code === charCodes.leftCurlyBrace && next === charCodes.verticalBar) { return this.finishOp(tt.braceBarL, 2); } else if ( @@ -1995,14 +2011,14 @@ export default (superClass: Class): Class => checkLVal( expr: N.Expression, - isBinding: ?boolean, + bindingType: ?BindingTypes = BIND_NONE, checkClashes: ?{ [key: string]: boolean }, contextDescription: string, ): void { if (expr.type !== "TypeCastExpression") { return super.checkLVal( expr, - isBinding, + bindingType, checkClashes, contextDescription, ); @@ -2047,6 +2063,7 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isConstructor: boolean, + allowsDirectSuper: boolean, ): void { if ((method: $FlowFixMe).variance) { this.unexpected((method: $FlowFixMe).variance.start); @@ -2064,6 +2081,7 @@ export default (superClass: Class): Class => isGenerator, isAsync, isConstructor, + allowsDirectSuper, ); } @@ -2217,7 +2235,12 @@ export default (superClass: Class): Class => ? this.flowParseRestrictedIdentifier(true) : this.parseIdentifier(); - this.checkLVal(specifier.local, true, undefined, contextDescription); + this.checkLVal( + specifier.local, + BIND_LEXICAL, + undefined, + contextDescription, + ); node.specifiers.push(this.finishNode(specifier, type)); } @@ -2327,12 +2350,17 @@ export default (superClass: Class): Class => ); } - this.checkLVal(specifier.local, true, undefined, "import specifier"); + this.checkLVal( + specifier.local, + BIND_LEXICAL, + undefined, + "import specifier", + ); node.specifiers.push(this.finishNode(specifier, "ImportSpecifier")); } // parse function type parameters - function foo() {} - parseFunctionParams(node: N.Function): void { + parseFunctionParams(node: N.Function, allowModifiers?: boolean): void { // $FlowFixMe const kind = node.kind; if (kind !== "get" && kind !== "set" && this.isRelational("<")) { @@ -2340,7 +2368,7 @@ export default (superClass: Class): Class => /* allowDefault */ false, ); } - super.parseFunctionParams(node); + super.parseFunctionParams(node, allowModifiers); } // parse flow type annotations on variable declarator heads - let foo: string = bar @@ -2519,8 +2547,9 @@ export default (superClass: Class): Class => } } - checkFunctionNameAndParams( + checkParams( node: N.Function, + allowDuplicates: boolean, isArrowFunction: ?boolean, ): void { if ( @@ -2530,7 +2559,7 @@ export default (superClass: Class): Class => return; } - return super.checkFunctionNameAndParams(node, isArrowFunction); + return super.checkParams(node, allowDuplicates, isArrowFunction); } parseParenAndDistinguishExpression(canBeArrow: boolean): N.Expression { @@ -2590,6 +2619,7 @@ export default (superClass: Class): Class => startLoc: Position, noCalls: ?boolean, subscriptState: N.ParseSubscriptState, + maybeAsyncArrow: boolean, ): N.Expression { if (this.match(tt.questionDot) && this.isLookaheadRelational("<")) { this.expectPlugin("optionalChaining"); @@ -2642,6 +2672,7 @@ export default (superClass: Class): Class => startLoc, noCalls, subscriptState, + maybeAsyncArrow, ); } @@ -2679,7 +2710,7 @@ export default (superClass: Class): Class => } readToken_mult_modulo(code: number): void { - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if ( code === charCodes.asterisk && next === charCodes.slash && @@ -2695,7 +2726,7 @@ export default (superClass: Class): Class => } readToken_pipe_amp(code: number): void { - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if ( code === charCodes.verticalBar && next === charCodes.rightCurlyBrace @@ -2731,7 +2762,7 @@ export default (superClass: Class): Class => } if (this.state.hasFlowComment) { - const end = this.state.input.indexOf("*-/", (this.state.pos += 2)); + const end = this.input.indexOf("*-/", (this.state.pos += 2)); if (end === -1) this.raise(this.state.pos - 2, "Unterminated comment"); this.state.pos = end + 3; return; @@ -2745,22 +2776,20 @@ export default (superClass: Class): Class => let shiftToFirstNonWhiteSpace = 2; while ( [charCodes.space, charCodes.tab].includes( - this.state.input.charCodeAt(pos + shiftToFirstNonWhiteSpace), + this.input.charCodeAt(pos + shiftToFirstNonWhiteSpace), ) ) { shiftToFirstNonWhiteSpace++; } - const ch2 = this.state.input.charCodeAt(shiftToFirstNonWhiteSpace + pos); - const ch3 = this.state.input.charCodeAt( - shiftToFirstNonWhiteSpace + pos + 1, - ); + const ch2 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos); + const ch3 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos + 1); if (ch2 === charCodes.colon && ch3 === charCodes.colon) { return shiftToFirstNonWhiteSpace + 2; // check for /*:: } if ( - this.state.input.slice( + this.input.slice( shiftToFirstNonWhiteSpace + pos, shiftToFirstNonWhiteSpace + pos + 12, ) === "flow-include" @@ -2774,7 +2803,7 @@ export default (superClass: Class): Class => } hasFlowCommentCompletion(): void { - const end = this.state.input.indexOf("*/", this.state.pos); + const end = this.input.indexOf("*/", this.state.pos); if (end === -1) { this.raise(this.state.pos, "Unterminated comment"); } diff --git a/packages/babel-parser/src/plugins/jsx/index.js b/packages/babel-parser/src/plugins/jsx/index.js index a129c6d27a27..a2644915f1f7 100644 --- a/packages/babel-parser/src/plugins/jsx/index.js +++ b/packages/babel-parser/src/plugins/jsx/index.js @@ -14,6 +14,8 @@ import { isNewLine } from "../../util/whitespace"; const HEX_NUMBER = /^[\da-fA-F]+$/; const DECIMAL_NUMBER = /^\d+$/; +// Be aware that this file is always executed and not only when the plugin is enabled. +// Therefore this contexts and tokens do always exist. tc.j_oTag = new TokContext("...", true, true); @@ -79,11 +81,11 @@ export default (superClass: Class): Class => let out = ""; let chunkStart = this.state.pos; for (;;) { - if (this.state.pos >= this.state.length) { + if (this.state.pos >= this.length) { this.raise(this.state.start, "Unterminated JSX contents"); } - const ch = this.state.input.charCodeAt(this.state.pos); + const ch = this.input.charCodeAt(this.state.pos); switch (ch) { case charCodes.lessThan: @@ -95,18 +97,18 @@ export default (superClass: Class): Class => } return super.getTokenFromCode(ch); } - out += this.state.input.slice(chunkStart, this.state.pos); + out += this.input.slice(chunkStart, this.state.pos); return this.finishToken(tt.jsxText, out); case charCodes.ampersand: - out += this.state.input.slice(chunkStart, this.state.pos); + out += this.input.slice(chunkStart, this.state.pos); out += this.jsxReadEntity(); chunkStart = this.state.pos; break; default: if (isNewLine(ch)) { - out += this.state.input.slice(chunkStart, this.state.pos); + out += this.input.slice(chunkStart, this.state.pos); out += this.jsxReadNewLine(true); chunkStart = this.state.pos; } else { @@ -117,12 +119,12 @@ export default (superClass: Class): Class => } jsxReadNewLine(normalizeCRLF: boolean): string { - const ch = this.state.input.charCodeAt(this.state.pos); + const ch = this.input.charCodeAt(this.state.pos); let out; ++this.state.pos; if ( ch === charCodes.carriageReturn && - this.state.input.charCodeAt(this.state.pos) === charCodes.lineFeed + this.input.charCodeAt(this.state.pos) === charCodes.lineFeed ) { ++this.state.pos; out = normalizeCRLF ? "\n" : "\r\n"; @@ -139,25 +141,25 @@ export default (superClass: Class): Class => let out = ""; let chunkStart = ++this.state.pos; for (;;) { - if (this.state.pos >= this.state.length) { + if (this.state.pos >= this.length) { this.raise(this.state.start, "Unterminated string constant"); } - const ch = this.state.input.charCodeAt(this.state.pos); + const ch = this.input.charCodeAt(this.state.pos); if (ch === quote) break; if (ch === charCodes.ampersand) { - out += this.state.input.slice(chunkStart, this.state.pos); + out += this.input.slice(chunkStart, this.state.pos); out += this.jsxReadEntity(); chunkStart = this.state.pos; } else if (isNewLine(ch)) { - out += this.state.input.slice(chunkStart, this.state.pos); + out += this.input.slice(chunkStart, this.state.pos); out += this.jsxReadNewLine(false); chunkStart = this.state.pos; } else { ++this.state.pos; } } - out += this.state.input.slice(chunkStart, this.state.pos++); + out += this.input.slice(chunkStart, this.state.pos++); return this.finishToken(tt.string, out); } @@ -165,11 +167,11 @@ export default (superClass: Class): Class => let str = ""; let count = 0; let entity; - let ch = this.state.input[this.state.pos]; + let ch = this.input[this.state.pos]; const startPos = ++this.state.pos; - while (this.state.pos < this.state.length && count++ < 10) { - ch = this.state.input[this.state.pos++]; + while (this.state.pos < this.length && count++ < 10) { + ch = this.input[this.state.pos++]; if (ch === ";") { if (str[0] === "#") { if (str[1] === "x") { @@ -208,11 +210,11 @@ export default (superClass: Class): Class => let ch; const start = this.state.pos; do { - ch = this.state.input.charCodeAt(++this.state.pos); + ch = this.input.charCodeAt(++this.state.pos); } while (isIdentifierChar(ch) || ch === charCodes.dash); return this.finishToken( tt.jsxName, - this.state.input.slice(start, this.state.pos), + this.input.slice(start, this.state.pos), ); } @@ -508,8 +510,7 @@ export default (superClass: Class): Class => return this.jsxParseElement(); } else if ( this.isRelational("<") && - this.state.input.charCodeAt(this.state.pos) !== - charCodes.exclamationMark + this.input.charCodeAt(this.state.pos) !== charCodes.exclamationMark ) { // In case we encounter an lt token here it will always be the start of // jsx as the lt sign is not allowed in places that expect an expression @@ -550,8 +551,7 @@ export default (superClass: Class): Class => if ( code === charCodes.lessThan && this.state.exprAllowed && - this.state.input.charCodeAt(this.state.pos + 1) !== - charCodes.exclamationMark + this.input.charCodeAt(this.state.pos + 1) !== charCodes.exclamationMark ) { ++this.state.pos; return this.finishToken(tt.jsxTagStart); diff --git a/packages/babel-parser/src/plugins/placeholders.js b/packages/babel-parser/src/plugins/placeholders.js new file mode 100644 index 000000000000..3a61a255595f --- /dev/null +++ b/packages/babel-parser/src/plugins/placeholders.js @@ -0,0 +1,314 @@ +// @flow + +import * as charCodes from "charcodes"; + +import { types as tt, TokenType } from "../tokenizer/types"; +import type Parser from "../parser"; +import * as N from "../types"; + +tt.placeholder = new TokenType("%%", { startsExpr: true }); + +export type PlaceholderTypes = + | "Identifier" + | "StringLiteral" + | "Expression" + | "Statement" + | "Declaration" + | "BlockStatement" + | "ClassBody" + | "Pattern"; + +// $PropertyType doesn't support enums. Use a fake "switch" (GetPlaceholderNode) +//type MaybePlaceholder = $PropertyType | N.Placeholder; + +type _Switch = $Call< + ( + $ElementType<$ElementType, 0>, + ) => $ElementType<$ElementType, 1>, + Value, +>; +type $Switch = _Switch; + +type NodeOf = $Switch< + T, + [ + ["Identifier", N.Identifier], + ["StringLiteral", N.StringLiteral], + ["Expression", N.Expression], + ["Statement", N.Statement], + ["Declaration", N.Declaration], + ["BlockStatement", N.BlockStatement], + ["ClassBody", N.ClassBody], + ["Pattern", N.Pattern], + ], +>; + +// Placeholder breaks everything, because its type is incompatible with +// the substituted nodes. +type MaybePlaceholder = NodeOf; // | Placeholder + +export default (superClass: Class): Class => + class extends superClass { + parsePlaceholder( + expectedNode: T, + ): /*?N.Placeholder*/ ?MaybePlaceholder { + if (this.match(tt.placeholder)) { + const node = this.startNode(); + this.next(); + this.assertNoSpace("Unexpected space in placeholder."); + + // We can't use this.parseIdentifier because + // we don't want nested placeholders. + node.name = super.parseIdentifier(/* liberal */ true); + + this.assertNoSpace("Unexpected space in placeholder."); + this.expect(tt.placeholder); + return this.finishPlaceholder(node, expectedNode); + } + } + + finishPlaceholder( + node: N.Node, + expectedNode: T, + ): /*N.Placeholder*/ MaybePlaceholder { + node.expectedNode = expectedNode; + return this.finishNode(node, "Placeholder"); + } + + /* ============================================================ * + * tokenizer/index.js * + * ============================================================ */ + + getTokenFromCode(code: number) { + if ( + code === charCodes.percentSign && + this.input.charCodeAt(this.state.pos + 1) === charCodes.percentSign + ) { + return this.finishOp(tt.placeholder, 2); + } + + return super.getTokenFromCode(...arguments); + } + + /* ============================================================ * + * parser/expression.js * + * ============================================================ */ + + parseExprAtom(): MaybePlaceholder<"Expression"> { + return ( + this.parsePlaceholder("Expression") || super.parseExprAtom(...arguments) + ); + } + + parseIdentifier(): MaybePlaceholder<"Identifier"> { + // NOTE: This function only handles identifiers outside of + // expressions and binding patterns, since they are already + // handled by the parseExprAtom and parseBindingAtom functions. + // This is needed, for example, to parse "class %%NAME%% {}". + return ( + this.parsePlaceholder("Identifier") || + super.parseIdentifier(...arguments) + ); + } + + checkReservedWord(word: string): void { + // Sometimes we call #checkReservedWord(node.name), expecting + // that node is an Identifier. If it is a Placeholder, name + // will be undefined. + if (word !== undefined) super.checkReservedWord(...arguments); + } + + /* ============================================================ * + * parser/lval.js * + * ============================================================ */ + + parseBindingAtom(): MaybePlaceholder<"Pattern"> { + return ( + this.parsePlaceholder("Pattern") || super.parseBindingAtom(...arguments) + ); + } + + checkLVal(expr: N.Expression): void { + if (expr.type !== "Placeholder") super.checkLVal(...arguments); + } + + toAssignable(node: N.Node): N.Node { + if ( + node && + node.type === "Placeholder" && + node.expectedNode === "Expression" + ) { + node.expectedNode = "Pattern"; + return node; + } + return super.toAssignable(...arguments); + } + + /* ============================================================ * + * parser/statement.js * + * ============================================================ */ + + verifyBreakContinue(node: N.BreakStatement | N.ContinueStatement) { + if (node.label && node.label.type === "Placeholder") return; + super.verifyBreakContinue(...arguments); + } + + parseExpressionStatement( + node: MaybePlaceholder<"Statement">, + expr: N.Expression, + ): MaybePlaceholder<"Statement"> { + if ( + expr.type !== "Placeholder" || + (expr.extra && expr.extra.parenthesized) + ) { + return super.parseExpressionStatement(...arguments); + } + + if (this.match(tt.colon)) { + const stmt: N.LabeledStatement = node; + stmt.label = this.finishPlaceholder(expr, "Identifier"); + this.next(); + stmt.body = this.parseStatement("label"); + return this.finishNode(stmt, "LabeledStatement"); + } + + this.semicolon(); + + node.name = expr.name; + return this.finishPlaceholder(node, "Statement"); + } + + parseBlock(): MaybePlaceholder<"BlockStatement"> { + return ( + this.parsePlaceholder("BlockStatement") || + super.parseBlock(...arguments) + ); + } + + parseFunctionId(): ?MaybePlaceholder<"Identifier"> { + return ( + this.parsePlaceholder("Identifier") || + super.parseFunctionId(...arguments) + ); + } + + parseClass( + node: T, + isStatement: /* T === ClassDeclaration */ boolean, + optionalId?: boolean, + ): T { + const type = isStatement ? "ClassDeclaration" : "ClassExpression"; + + this.next(); + this.takeDecorators(node); + + const placeholder = this.parsePlaceholder("Identifier"); + if (placeholder) { + if ( + this.match(tt._extends) || + this.match(tt.placeholder) || + this.match(tt.braceL) + ) { + node.id = placeholder; + } else if (optionalId || !isStatement) { + node.id = null; + node.body = this.finishPlaceholder(placeholder, "ClassBody"); + return this.finishNode(node, type); + } else { + this.unexpected(null, "A class name is required"); + } + } else { + this.parseClassId(node, isStatement, optionalId); + } + + this.parseClassSuper(node); + node.body = + this.parsePlaceholder("ClassBody") || + this.parseClassBody(!!node.superClass); + return this.finishNode(node, type); + } + + parseExport(node: N.Node): N.Node { + const placeholder = this.parsePlaceholder("Identifier"); + if (!placeholder) return super.parseExport(...arguments); + + if (!this.isContextual("from") && !this.match(tt.comma)) { + // export %%DECL%%; + node.specifiers = []; + node.source = null; + node.declaration = this.finishPlaceholder(placeholder, "Declaration"); + return this.finishNode(node, "ExportNamedDeclaration"); + } + + // export %%NAME%% from "foo"; + this.expectPlugin("exportDefaultFrom"); + const specifier = this.startNode(); + specifier.exported = placeholder; + node.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; + + return super.parseExport(node); + } + + maybeParseExportDefaultSpecifier(node: N.Node): boolean { + if (node.specifiers && node.specifiers.length > 0) { + // "export %%NAME%%" has already been parsed by #parseExport. + return true; + } + return super.maybeParseExportDefaultSpecifier(...arguments); + } + + checkExport(node: N.ExportNamedDeclaration): void { + const { specifiers } = node; + if (specifiers && specifiers.length) { + node.specifiers = specifiers.filter( + node => node.exported.type === "Placeholder", + ); + } + super.checkExport(node); + node.specifiers = specifiers; + } + + parseImport( + node: N.Node, + ): N.ImportDeclaration | N.TsImportEqualsDeclaration { + const placeholder = this.parsePlaceholder("Identifier"); + if (!placeholder) return super.parseImport(...arguments); + + node.specifiers = []; + + if (!this.isContextual("from") && !this.match(tt.comma)) { + // import %%STRING%%; + node.source = this.finishPlaceholder(placeholder, "StringLiteral"); + this.semicolon(); + return this.finishNode(node, "ImportDeclaration"); + } + + // import %%DEFAULT%% ... + const specifier = this.startNodeAtNode(placeholder); + specifier.local = placeholder; + this.finishNode(specifier, "ImportDefaultSpecifier"); + node.specifiers.push(specifier); + + if (this.eat(tt.comma)) { + // import %%DEFAULT%%, * as ... + const hasStarImport = this.maybeParseStarImportSpecifier(node); + + // import %%DEFAULT%%, { ... + if (!hasStarImport) this.parseNamedImportSpecifiers(node); + } + + this.expectContextual("from"); + node.source = this.parseImportSource(); + this.semicolon(); + return this.finishNode(node, "ImportDeclaration"); + } + + parseImportSource(): MaybePlaceholder<"StringLiteral"> { + // import ... from %%STRING%%; + + return ( + this.parsePlaceholder("StringLiteral") || + super.parseImportSource(...arguments) + ); + } + }; diff --git a/packages/babel-parser/src/plugins/typescript.js b/packages/babel-parser/src/plugins/typescript.js index 1c0961ab9733..513d0f3a94c0 100644 --- a/packages/babel-parser/src/plugins/typescript.js +++ b/packages/babel-parser/src/plugins/typescript.js @@ -6,6 +6,7 @@ import { types as ct } from "../tokenizer/context"; import * as N from "../types"; import type { Pos, Position } from "../util/location"; import Parser from "../parser"; +import { type BindingTypes, BIND_NONE, SCOPE_OTHER } from "../util/scopeflags"; type TsModifier = | "readonly" @@ -569,7 +570,7 @@ export default (superClass: Class): Class => const restNode: N.TsRestType = this.startNode(); this.next(); // skips ellipsis restNode.typeAnnotation = this.tsParseType(); - this.checkCommaAfterRest(tt.bracketR, "type"); + this.checkCommaAfterRest(); return this.finishNode(restNode, "TSRestType"); } @@ -1071,6 +1072,8 @@ export default (superClass: Class): Class => tsParseModuleBlock(): N.TsModuleBlock { const node: N.TsModuleBlock = this.startNode(); + this.scope.enter(SCOPE_OTHER); + this.expect(tt.braceL); // Inside of a module block is considered "top-level", meaning it can have imports and exports. this.parseBlockOrModuleBlockBody( @@ -1079,6 +1082,7 @@ export default (superClass: Class): Class => /* topLevel */ true, /* end */ tt.braceR, ); + this.scope.exit(); return this.finishNode(node, "TSModuleBlock"); } @@ -1217,8 +1221,7 @@ export default (superClass: Class): Class => switch (starttype) { case tt._function: - this.next(); - return this.parseFunction(nany, /* isStatement */ true); + return this.parseFunctionStatement(nany); case tt._class: return this.parseClass( nany, @@ -1372,18 +1375,11 @@ export default (superClass: Class): Class => return undefined; } - const oldInAsync = this.state.inAsync; - const oldInGenerator = this.state.inGenerator; - this.state.inAsync = true; - this.state.inGenerator = false; - res.id = null; - res.generator = false; - res.expression = true; // May be set again by parseFunctionBody. - res.async = true; - this.parseFunctionBody(res, true); - this.state.inAsync = oldInAsync; - this.state.inGenerator = oldInGenerator; - return this.finishNode(res, "ArrowFunctionExpression"); + return this.parseArrowExpression( + res, + /* params are already set */ null, + /* async */ true, + ); } tsParseTypeArguments(): N.TsTypeParameterInstantiation { @@ -1476,10 +1472,9 @@ export default (superClass: Class): Class => parseFunctionBodyAndFinish( node: N.BodilessFunctionOrMethodBase, type: string, - allowExpressionBody?: boolean, + isMethod?: boolean = false, ): void { - // For arrow functions, `parseArrow` handles the return type itself. - if (!allowExpressionBody && this.match(tt.colon)) { + if (this.match(tt.colon)) { node.returnType = this.tsParseTypeOrTypePredicateAnnotation(tt.colon); } @@ -1494,7 +1489,7 @@ export default (superClass: Class): Class => return; } - super.parseFunctionBodyAndFinish(node, type, allowExpressionBody); + super.parseFunctionBodyAndFinish(node, type, isMethod); } parseSubscript( @@ -1503,6 +1498,7 @@ export default (superClass: Class): Class => startLoc: Position, noCalls: ?boolean, state: N.ParseSubscriptState, + maybeAsyncArrow: boolean, ): N.Expression { if (!this.hasPrecedingLineBreak() && this.match(tt.bang)) { this.state.exprAllowed = false; @@ -1565,7 +1561,14 @@ export default (superClass: Class): Class => if (result) return result; } - return super.parseSubscript(base, startPos, startLoc, noCalls, state); + return super.parseSubscript( + base, + startPos, + startLoc, + noCalls, + state, + maybeAsyncArrow, + ); } parseNewArguments(node: N.NewExpression): void { @@ -1721,11 +1724,12 @@ export default (superClass: Class): Class => classBody: N.ClassBody, member: any, state: { hadConstructor: boolean }, + constructorAllowsSuper: boolean, ): void { const accessibility = this.parseAccessModifier(); if (accessibility) member.accessibility = accessibility; - super.parseClassMember(classBody, member, state); + super.parseClassMember(classBody, member, state, constructorAllowsSuper); } parseClassMemberWithIsStatic( @@ -1733,6 +1737,7 @@ export default (superClass: Class): Class => member: any, state: { hadConstructor: boolean }, isStatic: boolean, + constructorAllowsSuper: boolean, ): void { const methodOrProp: N.ClassMethod | N.ClassProperty = member; const prop: N.ClassProperty = member; @@ -1773,7 +1778,13 @@ export default (superClass: Class): Class => return; } - super.parseClassMemberWithIsStatic(classBody, member, state, isStatic); + super.parseClassMemberWithIsStatic( + classBody, + member, + state, + isStatic, + constructorAllowsSuper, + ); } parsePostMemberNameModifiers( @@ -1923,6 +1934,7 @@ export default (superClass: Class): Class => isGenerator: boolean, isAsync: boolean, isConstructor: boolean, + allowsDirectSuper: boolean, ): void { const typeParameters = this.tsTryParseTypeParameters(); if (typeParameters) method.typeParameters = typeParameters; @@ -1932,6 +1944,7 @@ export default (superClass: Class): Class => isGenerator, isAsync, isConstructor, + allowsDirectSuper, ); } @@ -2154,7 +2167,7 @@ export default (superClass: Class): Class => checkLVal( expr: N.Expression, - isBinding: ?boolean, + bindingType: ?BindingTypes = BIND_NONE, checkClashes: ?{ [key: string]: boolean }, contextDescription: string, ): void { @@ -2167,7 +2180,7 @@ export default (superClass: Class): Class => case "TSParameterProperty": this.checkLVal( expr.parameter, - isBinding, + bindingType, checkClashes, "parameter property", ); @@ -2177,13 +2190,13 @@ export default (superClass: Class): Class => case "TSTypeAssertion": this.checkLVal( expr.expression, - isBinding, + bindingType, checkClashes, contextDescription, ); return; default: - super.checkLVal(expr, isBinding, checkClashes, contextDescription); + super.checkLVal(expr, bindingType, checkClashes, contextDescription); return; } } @@ -2263,8 +2276,18 @@ export default (superClass: Class): Class => ): $ReadOnlyArray { for (let i = 0; i < exprList.length; i++) { const expr = exprList[i]; - if (expr && expr.type === "TSTypeCastExpression") { - exprList[i] = this.typeCastToParameter(expr); + if (!expr) continue; + switch (expr.type) { + case "TSTypeCastExpression": + exprList[i] = this.typeCastToParameter(expr); + break; + case "TSAsExpression": + case "TSTypeAssertion": + this.raise( + expr.start, + "Unexpected type cast in parameter position.", + ); + break; } } return super.toAssignableList(exprList, isBinding, contextDescription); @@ -2321,4 +2344,17 @@ export default (superClass: Class): Class => if (typeArguments) node.typeParameters = typeArguments; return super.jsxParseOpeningElementAfterName(node); } + + getGetterSetterExpectedParamCount( + method: N.ObjectMethod | N.ClassMethod, + ): number { + const baseCount = super.getGetterSetterExpectedParamCount(method); + const firstParam = method.params[0]; + const hasContextParam = + firstParam && + firstParam.type === "Identifier" && + firstParam.name === "this"; + + return hasContextParam ? baseCount + 1 : baseCount; + } }; diff --git a/packages/babel-parser/src/tokenizer/context.js b/packages/babel-parser/src/tokenizer/context.js index 6add1a4cf7d3..1d8108874f4a 100644 --- a/packages/babel-parser/src/tokenizer/context.js +++ b/packages/babel-parser/src/tokenizer/context.js @@ -60,7 +60,7 @@ tt.name.updateContext = function(prevType) { if (prevType !== tt.dot) { if ( (this.state.value === "of" && !this.state.exprAllowed) || - (this.state.value === "yield" && this.state.inGenerator) + (this.state.value === "yield" && this.scope.inGenerator) ) { allowed = true; } @@ -107,9 +107,7 @@ tt._function.updateContext = tt._class.updateContext = function(prevType) { prevType !== tt._else && !( prevType === tt._return && - lineBreak.test( - this.state.input.slice(this.state.lastTokEnd, this.state.start), - ) + lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start)) ) && !( (prevType === tt.colon || prevType === tt.braceL) && diff --git a/packages/babel-parser/src/tokenizer/index.js b/packages/babel-parser/src/tokenizer/index.js index f576698130f5..084a7ff9335b 100644 --- a/packages/babel-parser/src/tokenizer/index.js +++ b/packages/babel-parser/src/tokenizer/index.js @@ -116,7 +116,9 @@ export default class Tokenizer extends LocationParser { constructor(options: Options, input: string) { super(); this.state = new State(); - this.state.init(options, input); + this.state.init(options); + this.input = input; + this.length = input.length; this.isLookahead = false; } @@ -175,7 +177,7 @@ export default class Tokenizer extends LocationParser { this.state.pos = this.state.start; while (this.state.pos < this.state.lineStart) { this.state.lineStart = - this.state.input.lastIndexOf("\n", this.state.lineStart - 2) + 1; + this.input.lastIndexOf("\n", this.state.lineStart - 2) + 1; --this.state.curLine; } this.nextToken(); @@ -196,7 +198,7 @@ export default class Tokenizer extends LocationParser { this.state.octalPosition = null; this.state.start = this.state.pos; this.state.startLoc = this.state.curPosition(); - if (this.state.pos >= this.state.length) { + if (this.state.pos >= this.length) { this.finishToken(tt.eof); return; } @@ -204,7 +206,7 @@ export default class Tokenizer extends LocationParser { if (curContext.override) { curContext.override(this); } else { - this.getTokenFromCode(this.state.input.codePointAt(this.state.pos)); + this.getTokenFromCode(this.input.codePointAt(this.state.pos)); } } @@ -234,14 +236,14 @@ export default class Tokenizer extends LocationParser { skipBlockComment(): void { const startLoc = this.state.curPosition(); const start = this.state.pos; - const end = this.state.input.indexOf("*/", (this.state.pos += 2)); + const end = this.input.indexOf("*/", (this.state.pos += 2)); if (end === -1) this.raise(this.state.pos - 2, "Unterminated comment"); this.state.pos = end + 2; lineBreakG.lastIndex = start; let match; while ( - (match = lineBreakG.exec(this.state.input)) && + (match = lineBreakG.exec(this.input)) && match.index < this.state.pos ) { ++this.state.curLine; @@ -250,7 +252,7 @@ export default class Tokenizer extends LocationParser { this.pushComment( true, - this.state.input.slice(start + 2, end), + this.input.slice(start + 2, end), start, this.state.pos, startLoc, @@ -261,22 +263,22 @@ export default class Tokenizer extends LocationParser { skipLineComment(startSkip: number): void { const start = this.state.pos; const startLoc = this.state.curPosition(); - let ch = this.state.input.charCodeAt((this.state.pos += startSkip)); - if (this.state.pos < this.state.length) { + let ch = this.input.charCodeAt((this.state.pos += startSkip)); + if (this.state.pos < this.length) { while ( ch !== charCodes.lineFeed && ch !== charCodes.carriageReturn && ch !== charCodes.lineSeparator && ch !== charCodes.paragraphSeparator && - ++this.state.pos < this.state.length + ++this.state.pos < this.length ) { - ch = this.state.input.charCodeAt(this.state.pos); + ch = this.input.charCodeAt(this.state.pos); } } this.pushComment( false, - this.state.input.slice(start + startSkip, this.state.pos), + this.input.slice(start + startSkip, this.state.pos), start, this.state.pos, startLoc, @@ -288,8 +290,8 @@ export default class Tokenizer extends LocationParser { // whitespace and comments, and. skipSpace(): void { - loop: while (this.state.pos < this.state.length) { - const ch = this.state.input.charCodeAt(this.state.pos); + loop: while (this.state.pos < this.length) { + const ch = this.input.charCodeAt(this.state.pos); switch (ch) { case charCodes.space: case charCodes.nonBreakingSpace: @@ -298,8 +300,7 @@ export default class Tokenizer extends LocationParser { break; case charCodes.carriageReturn: if ( - this.state.input.charCodeAt(this.state.pos + 1) === - charCodes.lineFeed + this.input.charCodeAt(this.state.pos + 1) === charCodes.lineFeed ) { ++this.state.pos; } @@ -313,7 +314,7 @@ export default class Tokenizer extends LocationParser { break; case charCodes.slash: - switch (this.state.input.charCodeAt(this.state.pos + 1)) { + switch (this.input.charCodeAt(this.state.pos + 1)) { case charCodes.asterisk: this.skipBlockComment(); break; @@ -368,7 +369,7 @@ export default class Tokenizer extends LocationParser { } const nextPos = this.state.pos + 1; - const next = this.state.input.charCodeAt(nextPos); + const next = this.input.charCodeAt(nextPos); if (next >= charCodes.digit0 && next <= charCodes.digit9) { this.raise(this.state.pos, "Unexpected digit after hash token"); } @@ -391,13 +392,13 @@ export default class Tokenizer extends LocationParser { } readToken_dot(): void { - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if (next >= charCodes.digit0 && next <= charCodes.digit9) { this.readNumber(true); return; } - const next2 = this.state.input.charCodeAt(this.state.pos + 2); + const next2 = this.input.charCodeAt(this.state.pos + 2); if (next === charCodes.dot && next2 === charCodes.dot) { this.state.pos += 3; this.finishToken(tt.ellipsis); @@ -415,7 +416,7 @@ export default class Tokenizer extends LocationParser { return; } - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if (next === charCodes.equalsTo) { this.finishOp(tt.assign, 2); } else { @@ -424,12 +425,12 @@ export default class Tokenizer extends LocationParser { } readToken_interpreter(): boolean { - if (this.state.pos !== 0 || this.state.length < 2) return false; + if (this.state.pos !== 0 || this.length < 2) return false; const start = this.state.pos; this.state.pos += 1; - let ch = this.state.input.charCodeAt(this.state.pos); + let ch = this.input.charCodeAt(this.state.pos); if (ch !== charCodes.exclamationMark) return false; while ( @@ -437,12 +438,12 @@ export default class Tokenizer extends LocationParser { ch !== charCodes.carriageReturn && ch !== charCodes.lineSeparator && ch !== charCodes.paragraphSeparator && - ++this.state.pos < this.state.length + ++this.state.pos < this.length ) { - ch = this.state.input.charCodeAt(this.state.pos); + ch = this.input.charCodeAt(this.state.pos); } - const value = this.state.input.slice(start + 2, this.state.pos); + const value = this.input.slice(start + 2, this.state.pos); this.finishToken(tt.interpreterDirective, value); @@ -453,13 +454,13 @@ export default class Tokenizer extends LocationParser { // '%*' let type = code === charCodes.asterisk ? tt.star : tt.modulo; let width = 1; - let next = this.state.input.charCodeAt(this.state.pos + 1); + let next = this.input.charCodeAt(this.state.pos + 1); const exprAllowed = this.state.exprAllowed; // Exponentiation operator ** if (code === charCodes.asterisk && next === charCodes.asterisk) { width++; - next = this.state.input.charCodeAt(this.state.pos + 2); + next = this.input.charCodeAt(this.state.pos + 2); type = tt.exponent; } @@ -473,12 +474,10 @@ export default class Tokenizer extends LocationParser { readToken_pipe_amp(code: number): void { // '||' '&&' '||=' '&&=' - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if (next === code) { - if ( - this.state.input.charCodeAt(this.state.pos + 2) === charCodes.equalsTo - ) { + if (this.input.charCodeAt(this.state.pos + 2) === charCodes.equalsTo) { this.finishOp(tt.assign, 3); } else { this.finishOp( @@ -510,7 +509,7 @@ export default class Tokenizer extends LocationParser { readToken_caret(): void { // '^' - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if (next === charCodes.equalsTo) { this.finishOp(tt.assign, 2); } else { @@ -520,17 +519,14 @@ export default class Tokenizer extends LocationParser { readToken_plus_min(code: number): void { // '+-' - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); if (next === code) { if ( next === charCodes.dash && !this.inModule && - this.state.input.charCodeAt(this.state.pos + 2) === - charCodes.greaterThan && - lineBreak.test( - this.state.input.slice(this.state.lastTokEnd, this.state.pos), - ) + this.input.charCodeAt(this.state.pos + 2) === charCodes.greaterThan && + lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.pos)) ) { // A `-->` line comment this.skipLineComment(3); @@ -551,20 +547,16 @@ export default class Tokenizer extends LocationParser { readToken_lt_gt(code: number): void { // '<>' - const next = this.state.input.charCodeAt(this.state.pos + 1); + const next = this.input.charCodeAt(this.state.pos + 1); let size = 1; if (next === code) { size = code === charCodes.greaterThan && - this.state.input.charCodeAt(this.state.pos + 2) === - charCodes.greaterThan + this.input.charCodeAt(this.state.pos + 2) === charCodes.greaterThan ? 3 : 2; - if ( - this.state.input.charCodeAt(this.state.pos + size) === - charCodes.equalsTo - ) { + if (this.input.charCodeAt(this.state.pos + size) === charCodes.equalsTo) { this.finishOp(tt.assign, size + 1); return; } @@ -576,8 +568,8 @@ export default class Tokenizer extends LocationParser { next === charCodes.exclamationMark && code === charCodes.lessThan && !this.inModule && - this.state.input.charCodeAt(this.state.pos + 2) === charCodes.dash && - this.state.input.charCodeAt(this.state.pos + 3) === charCodes.dash + this.input.charCodeAt(this.state.pos + 2) === charCodes.dash && + this.input.charCodeAt(this.state.pos + 3) === charCodes.dash ) { // `