diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c7f92f01e003..31f33180bf2f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,7 +1,5 @@ "use strict"; -const path = require("path"); - const cjsGlobals = ["__dirname", "__filename", "require", "module", "exports"]; const testFiles = [ @@ -105,21 +103,6 @@ module.exports = { eqeqeq: ["error", "always", { null: "ignore" }], }, }, - { - files: ["packages/babel-parser/src/**/*.{js,ts}"], - rules: { - "@babel/development-internal/dry-error-messages": [ - "error", - { - errorModule: path.resolve( - __dirname, - "packages/babel-parser/src/parser/error.js" - ), - }, - ], - "@babel/development-internal/report-error-message-format": "error", - }, - }, { files: ["packages/babel-helpers/src/helpers/**.js"], rules: { diff --git a/eslint/babel-eslint-plugin-development-internal/README.md b/eslint/babel-eslint-plugin-development-internal/README.md index 2d8fa5e626ac..d4e9088977e5 100644 --- a/eslint/babel-eslint-plugin-development-internal/README.md +++ b/eslint/babel-eslint-plugin-development-internal/README.md @@ -24,51 +24,6 @@ The plugin can be loaded in your `.eslintrc.*` configuration file as follows: (n ## Rules -### `@babel/development-internal/dry-error-messages` - -Intended for use in `packages/babel-parser/src/**/*`. When enabled, this rule warns when `this.raise()` invocations raise errors that are not imported from a designated error module. - -Accepts an object configuration option: - -```ts -{ - errorModule: string -} -``` - -`errorModule` (required): The rule expects either an absolute path or a module name (for a module in `node_modules`). Please note that the rule will not check anything if` errorModule` is not given. - -Example configuration: - -```js -{ - rules: { - "@babel/development-internal/dry-error-messages": [ - "error", - { - errorModule: "@babel/shared-error-messages" - } - ] - } -} -``` -and -```js -{ - rules: { - "@babel/development-internal/dry-error-messages": [ - "error", - { - errorModule: path.resolve( - __dirname, - "packages/shared-error-messages/lib/index.js" - ) - } - ] - } -} -``` - ### `@babel/development-internal/report-error-message-format` This rule is inspired by https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/report-message-format.md. diff --git a/eslint/babel-eslint-plugin-development-internal/src/index.cjs b/eslint/babel-eslint-plugin-development-internal/src/index.cjs index 107f4696ae5f..b7b23f2a24b7 100644 --- a/eslint/babel-eslint-plugin-development-internal/src/index.cjs +++ b/eslint/babel-eslint-plugin-development-internal/src/index.cjs @@ -1,8 +1,6 @@ -const dryErrorMessages = require("./rules/dry-error-messages.cjs"); const reportErrorMessageFormat = require("./rules/report-error-message-format.cjs"); const rules = { - "dry-error-messages": dryErrorMessages, "report-error-message-format": reportErrorMessageFormat, }; diff --git a/eslint/babel-eslint-plugin-development-internal/src/rules/dry-error-messages.cjs b/eslint/babel-eslint-plugin-development-internal/src/rules/dry-error-messages.cjs deleted file mode 100644 index a3e28036208f..000000000000 --- a/eslint/babel-eslint-plugin-development-internal/src/rules/dry-error-messages.cjs +++ /dev/null @@ -1,169 +0,0 @@ -const path = require("path"); - -const REL_PATH_REGEX = /^\.{1,2}/; - -function isRelativePath(filePath) { - return REL_PATH_REGEX.test(filePath); -} - -function resolveAbsolutePath(currentFilePath, moduleToResolve) { - return isRelativePath(moduleToResolve) - ? path.resolve(path.dirname(currentFilePath), moduleToResolve) - : moduleToResolve; -} - -function isSourceErrorModule(currentFilePath, targetModulePath, src) { - for (const srcPath of [src, `${src}.js`, `${src}/index`, `${src}/index.js`]) { - if ( - path.normalize(resolveAbsolutePath(currentFilePath, targetModulePath)) === - path.normalize(resolveAbsolutePath(currentFilePath, srcPath)) - ) { - return true; - } - } - - return false; -} - -function isCurrentFileErrorModule(currentFilePath, errorModule) { - return currentFilePath === errorModule; -} - -function findIdNode(node) { - if (node.type === "Identifier") { - return node; - } - - if (node.type === "MemberExpression" && node.object.type === "Identifier") { - return node.object; - } - - return null; -} - -function findIdNodes(node) { - if (node.type === "ConditionalExpression") { - const consequent = findIdNode(node.consequent); - const alternate = findIdNode(node.alternate); - - if (consequent && alternate) { - return [consequent, alternate]; - } - } - - const idNode = findIdNode(node); - - if (idNode) { - return [idNode]; - } - - return null; -} - -function findReference(node, scope) { - let currentScope = scope; - - while (currentScope) { - const ref = currentScope.set.get(node.name); - - if (ref) { - return ref; - } - - currentScope = currentScope.upper; - } - - return null; -} - -function referencesImportedBinding(node, scope, bindings) { - const ref = findReference(node, scope); - - if (ref) { - const topLevelDef = ref.defs[0]; - - if (topLevelDef.type === "ImportBinding") { - const defNode = topLevelDef.node; - - for (const spec of bindings) { - if ( - spec.loc.start === defNode.loc.start && - spec.loc.end === defNode.loc.end - ) { - return true; - } - } - } - } - - return false; -} - -module.exports = { - meta: { - type: "suggestion", - docs: { - description: - "enforce @babel/parser's error messages to be consolidated in one module", - }, - schema: [ - { - type: "object", - properties: { - errorModule: { type: "string" }, - }, - additionalProperties: false, - required: ["errorModule"], - }, - ], - messages: { - mustBeImported: 'Error messages must be imported from "{{errorModule}}".', - }, - }, - create({ options, report, getFilename, getScope }) { - const [{ errorModule = "" } = {}] = options; - const filename = getFilename(); - const importedBindings = new Set(); - - if ( - // Do not run check if errorModule config option is not given. - !errorModule.length || - // Do not check the target error module file. - isCurrentFileErrorModule(filename, errorModule) - ) { - return {}; - } - - return { - // Check imports up front so that we don't have to check them for every ThrowStatement. - ImportDeclaration(node) { - if (isSourceErrorModule(filename, errorModule, node.source.value)) { - for (const spec of node.specifiers) { - importedBindings.add(spec); - } - } - }, - "CallExpression[callee.type='MemberExpression'][callee.object.type='ThisExpression'][callee.property.name='raise'][arguments.length>=2]"( - node, - ) { - const [errorMsgNode] = node.arguments; - const nodesToCheck = findIdNodes(errorMsgNode); - - if ( - Array.isArray(nodesToCheck) && - nodesToCheck.every(node => - referencesImportedBinding(node, getScope(), importedBindings), - ) - ) { - return; - } - - report({ - node: errorMsgNode, - messageId: "mustBeImported", - data: { errorModule }, - }); - }, - }; - }, -}; diff --git a/eslint/babel-eslint-plugin-development-internal/test/rules/dry-error-messages.js b/eslint/babel-eslint-plugin-development-internal/test/rules/dry-error-messages.js deleted file mode 100644 index 609af895772b..000000000000 --- a/eslint/babel-eslint-plugin-development-internal/test/rules/dry-error-messages.js +++ /dev/null @@ -1,678 +0,0 @@ -import path from "path"; -import rule from "../../lib/rules/dry-error-messages.cjs"; -import RuleTester from "../../../babel-eslint-shared-fixtures/utils/RuleTester.js"; -import { fileURLToPath } from "url"; - -const dirname = path.dirname(fileURLToPath(import.meta.url)); - -const FILENAME = path.resolve(dirname, "test/lib/index.js"); -const ERRORS_MODULE = "errorsModule"; -const MODULE_SAME_DIR = path.resolve(dirname, "test/lib/errorsModule.js"); -const MODULE_PARENT_DIR = path.resolve(dirname, "test/errorsModule.js"); - -const ruleTester = new RuleTester(); - -ruleTester.run("dry-error-messages", rule, { - valid: [ - // Ignores malformed `this.raise` invocations. - { - filename: FILENAME, - code: "this.raise(loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "this.notRaise(loc, 'Uh oh');", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "throw new Error(this.raise('Uh oh'));", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "this.raise(() => { throw new Error('Uh oh') });", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "throw new Error('Uh oh')", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "throw this.createError('Uh oh')", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "throw this.error", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "this.raise", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "throw obj.error", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "throw obj.raise", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import { Errors } from 'errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import { Errors } from './errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import { Errors } from '../errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from 'errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from './errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from '../errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import { Errors } from 'errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import { Errors } from './errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import { Errors } from '../errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from 'errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from './errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from '../errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import Errors from 'errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import Errors from './errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import Errors from '../errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from './errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from '../errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from 'errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from './errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from '../errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import Errors from 'errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import Errors from './errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import Errors from '../errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from './errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from '../errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from 'errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from './errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from '../errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - }, - - // Do not warn when file linted is error module. - { - filename: FILENAME, - code: "this.raise( 'Oh no!', loc);", - options: [{ errorModule: FILENAME }], - }, - { - filename: MODULE_SAME_DIR, - code: "this.raise('Oh no!', loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - }, - - // Do not warn if second argument is missing - { - filename: FILENAME, - code: "this.raise(loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - - // Support ternary as second argument - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'errorsModule'; this.raise(a ? Errors.someErrorMessage : Errors.someOtherErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - }, - ], - invalid: [ - { - filename: FILENAME, - code: "this.raise(new Error('Uh oh'), loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "throw this.raise(new Error('Uh oh'), loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "const Errors = { someErrorMessage: 'Uh oh!' }; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from 'errorsModule'; this.raise('Uh oh!', loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from 'errorsModule'; const msg = 'Uh oh!'; this.raise(msg, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from 'not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from './not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from '../not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from 'not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from './not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from '../not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from 'not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from './not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { Errors } from '../not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from 'not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from './not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import { NotErrors, Errors } from '../not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors from 'not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors from './not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors from '../not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from './not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from '../not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from 'not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from './not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from '../not-errorsModule'; this.raise(Errors.someErrorMessage, loc);", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors from 'not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors from './not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors from '../not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from './not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from '../not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from 'not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from './not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_SAME_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_SAME_DIR }, - }, - ], - }, - { - filename: FILENAME, - code: "import NotErrors, { Errors } from '../not-errorsModule'; function fn() { this.raise(Errors.someErrorMessage, loc); }", - options: [{ errorModule: MODULE_PARENT_DIR }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: MODULE_PARENT_DIR }, - }, - ], - }, - - // Should error if either part of a ternary isn't from error module - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'errorsModule'; this.raise(a ? Errors.someErrorMessage : 'hello', loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'errorsModule'; this.raise( a ? 'hello' : Errors.someErrorMessage, loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - { - filename: FILENAME, - code: "import Errors, { NotErrors } from 'errorsModule'; this.raise(a ? 'hello' : 'world', loc);", - options: [{ errorModule: ERRORS_MODULE }], - errors: [ - { - messageId: "mustBeImported", - data: { errorModule: ERRORS_MODULE }, - }, - ], - }, - ], -}); diff --git a/lib/babel-packages.js.flow b/lib/babel-packages.js.flow index dab37130a96a..bffca14e73d5 100644 --- a/lib/babel-packages.js.flow +++ b/lib/babel-packages.js.flow @@ -39,7 +39,7 @@ declare module "@babel/helper-optimise-call-expression" { declare export default function optimiseCallExpression( callee: BabelNodeExpression, thisNode: BabelNodeExpression, - args: $Readonly>, + args: $ReadOnly>, optional: boolean ): BabelNodeCallExpression | BabelNodeOptionalCallExpression; } @@ -154,7 +154,7 @@ declare module "@babel/helper-validator-option" { } declare function findSuggestion( str: string, - arr: $ReadonlyArray + arr: $ReadOnlyArray ): string; } diff --git a/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/input.js b/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/input.js index fbaa3a91c4a4..6bb94752bf01 100644 --- a/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/input.js +++ b/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/input.js @@ -1 +1 @@ -const x: abstract new () => void; +declare const x: abstract new () => void; diff --git a/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/output.js b/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/output.js index 5c520bc29f3c..71f417390e21 100644 --- a/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/output.js +++ b/packages/babel-generator/test/fixtures/typescript/abstract-constructor-signature-types/output.js @@ -1 +1 @@ -const x: abstract new () => void; \ No newline at end of file +declare const x: abstract new () => void; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/typescript/const-no-initializer/input.js b/packages/babel-generator/test/fixtures/typescript/const-no-initializer/input.js index 8caae258b97c..7a86e6a93360 100644 --- a/packages/babel-generator/test/fixtures/typescript/const-no-initializer/input.js +++ b/packages/babel-generator/test/fixtures/typescript/const-no-initializer/input.js @@ -1 +1 @@ -const x: number; +declare const x: number; diff --git a/packages/babel-generator/test/fixtures/typescript/const-no-initializer/output.js b/packages/babel-generator/test/fixtures/typescript/const-no-initializer/output.js index bee7698244b8..aa2fb21074c8 100644 --- a/packages/babel-generator/test/fixtures/typescript/const-no-initializer/output.js +++ b/packages/babel-generator/test/fixtures/typescript/const-no-initializer/output.js @@ -1 +1 @@ -const x: number; \ No newline at end of file +declare const x: number; \ No newline at end of file diff --git a/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/input.js b/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/input.js index c1fc7b6057c6..138d69779a4b 100644 --- a/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/input.js +++ b/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/input.js @@ -1 +1 @@ -const x: new () => void; +declare const x: new () => void; diff --git a/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/output.js b/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/output.js index 218e27130c01..cc6549eedebf 100644 --- a/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/output.js +++ b/packages/babel-generator/test/fixtures/typescript/constructor-signature-types/output.js @@ -1 +1 @@ -const x: new () => void; \ No newline at end of file +declare const x: new () => void; \ No newline at end of file diff --git a/packages/babel-parser/src/parse-error.js b/packages/babel-parser/src/parse-error.js new file mode 100644 index 000000000000..0cd9cbfc12d8 --- /dev/null +++ b/packages/babel-parser/src/parse-error.js @@ -0,0 +1,196 @@ +// @flow + +import { Position } from "./util/location"; +import type { NodeBase } from "./types"; +import { + instantiate, + type ParseErrorCode, + ParseErrorCodes, + type ParseErrorCredentials, +} from "./parse-error/credentials"; + +// Babel uses "normal" SyntaxErrors for it's errors, but adds some extra +// functionality. This functionality is defined in the +// `ParseErrorSpecification` interface below. We may choose to change to someday +// give our errors their own full-blown class, but until then this allow us to +// keep all the desirable properties of SyntaxErrors (like their name in stack +// traces, etc.), and also allows us to punt on any publicly facing +// class-hierarchy decisions until Babel 8. +interface ParseErrorSpecification { + // Look, these *could* be readonly, but then Flow complains when we initially + // set them. We could do a whole dance and make a special interface that's not + // readonly for when we create the error, then cast it to the readonly + // interface for public use, but the previous implementation didn't have them + // as readonly, so let's just not worry about it for now. + code: ParseErrorCode; + reasonCode: string; + syntaxPlugin?: string; + + missingPlugin?: string | string[]; + + loc: Position; + details: ErrorDetails; + + // We should consider removing this as it now just contains the same + // information as `loc.index`. + // pos: number; +} + +export type ParseError = SyntaxError & + ParseErrorSpecification; + +// By `ParseErrorConstructor`, we mean something like the new-less style +// `ErrorConstructor`[1], since `ParseError`'s are not themselves actually +// separate classes from `SyntaxError`'s. +// +// 1. https://github.com/microsoft/TypeScript/blob/v4.5.5/lib/lib.es5.d.ts#L1027 +export type ParseErrorConstructor = ({ + loc: Position, + details: ErrorDetails, +}) => ParseError; + +function toParseErrorConstructor({ + toMessage, + ...properties +}: ParseErrorCredentials): ParseErrorConstructor { + type ConstructorArgument = { loc: Position, details: ErrorDetails }; + return function constructor({ loc, details }: ConstructorArgument) { + return instantiate>( + SyntaxError, + { ...properties, loc }, + { + clone(overrides: { loc?: Position, details?: ErrorDetails } = {}) { + const loc = overrides.loc || {}; + return constructor({ + loc: new Position( + "line" in loc ? loc.line : this.loc.line, + "column" in loc ? loc.column : this.loc.column, + "index" in loc ? loc.index : this.loc.index, + ), + details: { ...this.details, ...overrides.details }, + }); + }, + details: { value: details, enumerable: false }, + message: { + get() { + return `${toMessage(this.details)} (${this.loc.line}:${ + this.loc.column + })`; + }, + set(value: string) { + Object.defineProperty(this, "message", { value }); + }, + }, + pos: { reflect: "loc.index", enumerable: true }, + missingPlugin: "missingPlugin" in details && { + reflect: "details.missingPlugin", + enumerable: true, + }, + }, + ); + }; +} + +// This part is tricky. You'll probably notice from the name of this function +// that it is supposed to return `ParseErrorCredentials`, but instead these. +// declarations seem to instead imply that they return +// `ParseErrorConstructor` instead. This is because in Flow we +// can't easily extract parameter types (either from functions, like with +// Typescript's Parameters utility type, or from generic types either). As +// such, this function does double duty: packaging up the credentials during +// its actual runtime operation, but pretending to return the +// `ParseErrorConstructor` that we won't actually have until later +// to the type system, avoiding the need to do so with $ObjMap (which doesn't +// work) in `ParseErrorEnum`. This hack won't be necessary when we switch to +// Typescript. +declare function toParseErrorCredentials( + T, + ?{ code?: ParseErrorCode, reasonCode?: string } | boolean, +): ParseErrorConstructor<{||}>; + +// ESLint seems to erroneously think that Flow's overloading syntax is an +// accidental redeclaration of the function: +// https://github.com/babel/eslint-plugin-babel/issues/162 +// eslint-disable-next-line no-redeclare +declare function toParseErrorCredentials( + (ErrorDetails) => string, + ?{ code?: ParseErrorCode, reasonCode?: string } | boolean, +): ParseErrorConstructor; + +// See comment about eslint and Flow overloading above. +// eslint-disable-next-line no-redeclare +export function toParseErrorCredentials(toMessageOrMessage, credentials) { + return { + toMessage: + typeof toMessageOrMessage === "string" + ? () => toMessageOrMessage + : toMessageOrMessage, + ...credentials, + }; +} + +// This is the templated form. +declare function ParseErrorEnum(string[]): typeof ParseErrorEnum; + +// See comment about eslint and Flow overloading above. +// eslint-disable-next-line no-redeclare +declare function ParseErrorEnum( + toParseErrorCredentials: (typeof toParseErrorCredentials) => T, + syntaxPlugin?: string, +): T; + +// You call `ParseErrorEnum` with a mapping from `ReasonCode`'s to either error +// messages, or `toMessage` functions that define additional necessary `details` +// needed by the `ParseError`: +// +// ParseErrorEnum`optionalSyntaxPlugin` (_ => ({ +// ErrorWithStaticMessage: _("message"), +// ErrorWithDynamicMessage: _<{ type: string }>(({ type }) => `${type}`), +// }); +// +// See comment about eslint and Flow overloading above. +// eslint-disable-next-line no-redeclare +export function ParseErrorEnum(argument, syntaxPlugin) { + // If the first parameter is an array, that means we were called with a tagged + // template literal. Extract the syntaxPlugin from this, and call again in + // the "normalized" form. + if (Array.isArray(argument)) { + return toParseErrorCredentialsMap => + ParseErrorEnum(toParseErrorCredentialsMap, argument[0]); + } + + const partialCredentials = argument(toParseErrorCredentials); + const ParseErrorConstructors = {}; + + for (const reasonCode of Object.keys(partialCredentials)) { + ParseErrorConstructors[reasonCode] = toParseErrorConstructor({ + code: ParseErrorCodes.SyntaxError, + reasonCode, + ...(syntaxPlugin ? { syntaxPlugin } : {}), + ...partialCredentials[reasonCode], + }); + } + + return ParseErrorConstructors; +} + +export type RaiseProperties = {| + ...ErrorDetails, + at: Position | NodeBase, +|}; + +import ModuleErrors from "./parse-error/module-errors"; +import StandardErrors from "./parse-error/standard-errors"; +import StrictModeErrors from "./parse-error/strict-mode-errors"; +import PipelineOperatorErrors from "./parse-error/pipeline-operator-errors"; + +export const Errors = { + ...ParseErrorEnum(ModuleErrors), + ...ParseErrorEnum(StandardErrors), + ...ParseErrorEnum(StrictModeErrors), + ...ParseErrorEnum`pipelineOperator`(PipelineOperatorErrors), +}; + +export type { LValAncestor } from "./parse-error/standard-errors"; + +export * from "./parse-error/credentials"; diff --git a/packages/babel-parser/src/parse-error/credentials.js b/packages/babel-parser/src/parse-error/credentials.js new file mode 100644 index 000000000000..1e758abb8731 --- /dev/null +++ b/packages/babel-parser/src/parse-error/credentials.js @@ -0,0 +1,64 @@ +// @flow + +export const ParseErrorCodes = Object.freeze({ + SyntaxError: "BABEL_PARSER_SYNTAX_ERROR", + SourceTypeModuleError: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED", +}); + +export type ParseErrorCode = $Values; + +export type SyntaxPlugin = + | "flow" + | "typescript" + | "jsx" + | "pipelineOperator" + | "placeholders"; + +export type ToMessage = (self: ErrorDetails) => string; + +export type ParseErrorCredentials = { + code: ParseErrorCode, + reasonCode: string, + syntaxPlugin?: SyntaxPlugin, + + toMessage: ToMessage, +}; + +const reflect = (keys: string[], last = keys.length - 1) => ({ + get() { + return keys.reduce((object, key) => object[key], this); + }, + set(value) { + keys.reduce( + (item, key, i) => (i === last ? (item[key] = value) : item[key]), + this, + ); + }, +}); + +const instantiate = ( + constructor: () => any, + properties: Object, + descriptors: Object, +) => + Object.keys(descriptors) + .map(key => [key, descriptors[key]]) + .filter(([, descriptor]) => !!descriptor) + .map(([key, descriptor]) => [ + key, + typeof descriptor === "function" + ? { value: descriptor, enumerable: false } + : typeof descriptor.reflect === "string" + ? { ...descriptor, ...reflect(descriptor.reflect.split(".")) } + : descriptor, + ]) + .reduce( + (instance, [key, descriptor]) => + Object.defineProperty(instance, key, { + configurable: true, + ...descriptor, + }), + Object.assign((new constructor(): T), properties), + ); + +export { instantiate }; diff --git a/packages/babel-parser/src/parse-error/module-errors.js b/packages/babel-parser/src/parse-error/module-errors.js new file mode 100644 index 000000000000..82f83c222731 --- /dev/null +++ b/packages/babel-parser/src/parse-error/module-errors.js @@ -0,0 +1,14 @@ +// @flow + +import { ParseErrorCodes, toParseErrorCredentials } from "../parse-error"; + +export default (_: typeof toParseErrorCredentials) => ({ + ImportMetaOutsideModule: _( + `import.meta may appear only with 'sourceType: "module"'`, + { code: ParseErrorCodes.SourceTypeModuleError }, + ), + ImportOutsideModule: _( + `'import' and 'export' may appear only with 'sourceType: "module"'`, + { code: ParseErrorCodes.SourceTypeModuleError }, + ), +}); diff --git a/packages/babel-parser/src/parse-error/pipeline-operator-errors.js b/packages/babel-parser/src/parse-error/pipeline-operator-errors.js new file mode 100644 index 000000000000..a5a66d6d5eb7 --- /dev/null +++ b/packages/babel-parser/src/parse-error/pipeline-operator-errors.js @@ -0,0 +1,60 @@ +// @flow + +import { toParseErrorCredentials } from "../parse-error"; +import toNodeDescription from "./to-node-description"; + +export const UnparenthesizedPipeBodyDescriptions = new Set([ + "ArrowFunctionExpression", + "AssignmentExpression", + "ConditionalExpression", + "YieldExpression", +]); + +export default (_: typeof toParseErrorCredentials) => ({ + // This error is only used by the smart-mix proposal + PipeBodyIsTighter: _( + "Unexpected yield after pipeline body; any yield expression acting as Hack-style pipe body must be parenthesized due to its loose operator precedence.", + ), + PipeTopicRequiresHackPipes: _( + 'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.', + ), + PipeTopicUnbound: _( + "Topic reference is unbound; it must be inside a pipe body.", + ), + PipeTopicUnconfiguredToken: _<{| token: string |}>( + ({ token }) => + `Invalid topic token ${token}. In order to use ${token} as a topic reference, the pipelineOperator plugin must be configured with { "proposal": "hack", "topicToken": "${token}" }.`, + ), + PipeTopicUnused: _( + "Hack-style pipe body does not contain a topic reference; Hack-style pipes must use topic at least once.", + ), + PipeUnparenthesizedBody: _<{| type: string |}>( + ({ type }) => + `Hack-style pipe body cannot be an unparenthesized ${toNodeDescription({ + type, + })}; please wrap it in parentheses.`, + ), + + // Messages whose codes start with “Pipeline” or “PrimaryTopic” + // are retained for backwards compatibility + // with the deprecated smart-mix pipe operator proposal plugin. + // They are subject to removal in a future major version. + PipelineBodyNoArrow: _( + 'Unexpected arrow "=>" after pipeline body; arrow function in pipeline body must be parenthesized.', + ), + PipelineBodySequenceExpression: _( + "Pipeline body may not be a comma-separated sequence expression.", + ), + PipelineHeadSequenceExpression: _( + "Pipeline head should not be a comma-separated sequence expression.", + ), + PipelineTopicUnused: _( + "Pipeline is in topic style but does not use topic reference.", + ), + PrimaryTopicNotAllowed: _( + "Topic reference was used in a lexical context without topic binding.", + ), + PrimaryTopicRequiresSmartPipeline: _( + 'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.', + ), +}); diff --git a/packages/babel-parser/src/parse-error/standard-errors.js b/packages/babel-parser/src/parse-error/standard-errors.js new file mode 100644 index 000000000000..b17d6bfc2ef0 --- /dev/null +++ b/packages/babel-parser/src/parse-error/standard-errors.js @@ -0,0 +1,364 @@ +// @flow + +import { toParseErrorCredentials } from "../parse-error"; +import toNodeDescription from "./to-node-description"; + +export type LValAncestor = + | { type: "UpdateExpression", prefix: boolean } + | { + type: + | "ArrayPattern" + | "AssignmentExpression" + | "CatchClause" + | "ForOfStatement" + | "FormalParameters" + | "ForInStatement" + | "ForStatement" + | "Identfier" + | "ObjectPattern" + | "RestElement" + | "VariableDeclarator", + }; + +export default (_: typeof toParseErrorCredentials) => ({ + AccessorIsGenerator: _<{| kind: "get" | "set" |}>( + ({ kind }) => `A ${kind}ter cannot be a generator.`, + ), + + ArgumentsInClass: _( + "'arguments' is only allowed in functions and class methods.", + ), + AsyncFunctionInSingleStatementContext: _( + "Async functions can only be declared at the top level or inside a block.", + ), + AwaitBindingIdentifier: _( + "Can not use 'await' as identifier inside an async function.", + ), + AwaitBindingIdentifierInStaticBlock: _( + "Can not use 'await' as identifier inside a static block.", + ), + AwaitExpressionFormalParameter: _( + "'await' is not allowed in async function parameters.", + ), + AwaitNotInAsyncContext: _( + "'await' is only allowed within async functions and at the top levels of modules.", + ), + AwaitNotInAsyncFunction: _("'await' is only allowed within async functions."), + BadGetterArity: _("A 'get' accesor must not have any formal parameters."), + BadSetterArity: _("A 'set' accesor must have exactly one formal parameter."), + BadSetterRestParameter: _( + "A 'set' accesor function argument must not be a rest parameter.", + ), + ConstructorClassField: _("Classes may not have a field named 'constructor'."), + ConstructorClassPrivateField: _( + "Classes may not have a private field named '#constructor'.", + ), + ConstructorIsAccessor: _("Class constructor may not be an accessor."), + ConstructorIsAsync: _("Constructor can't be an async function."), + ConstructorIsGenerator: _("Constructor can't be a generator."), + DeclarationMissingInitializer: _<{| kind: "const" | "destructuring" |}>( + ({ kind }) => `Missing initializer in ${kind} declaration.`, + ), + DecoratorBeforeExport: _( + "Decorators must be placed *before* the 'export' keyword. You can set the 'decoratorsBeforeExport' option to false to use the 'export @decorator class {}' syntax.", + ), + DecoratorConstructor: _( + "Decorators can't be used with a constructor. Did you mean '@dec class { ... }'?", + ), + DecoratorExportClass: _( + "Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.", + ), + DecoratorSemicolon: _("Decorators must not be followed by a semicolon."), + DecoratorStaticBlock: _("Decorators can't be used with a static block."), + DeletePrivateField: _("Deleting a private field is not allowed."), + DestructureNamedImport: _( + "ES2015 named imports do not destructure. Use another statement for destructuring after the import.", + ), + DuplicateConstructor: _("Duplicate constructor in the same class."), + DuplicateDefaultExport: _("Only one default export allowed per module."), + DuplicateExport: _<{| exportName: string |}>( + ({ exportName }) => + `\`${exportName}\` has already been exported. Exported identifiers must be unique.`, + ), + DuplicateProto: _("Redefinition of __proto__ property."), + DuplicateRegExpFlags: _("Duplicate regular expression flag."), + ElementAfterRest: _("Rest element must be last element."), + EscapedCharNotAnIdentifier: _("Invalid Unicode escape."), + ExportBindingIsString: _<{| localName: string, exportName: string |}>( + ({ localName, exportName }) => + `A string literal cannot be used as an exported binding without \`from\`.\n- Did you mean \`export { '${localName}' as '${exportName}' } from 'some-module'\`?`, + ), + ExportDefaultFromAsIdentifier: _( + "'from' is not allowed as an identifier after 'export default'.", + ), + + ForInOfLoopInitializer: _<{| type: "ForInStatement" | "ForOfStatement" |}>( + ({ type }) => + `'${ + type === "ForInStatement" ? "for-in" : "for-of" + }' loop variable declaration may not have an initializer.`, + ), + + ForOfAsync: _("The left-hand side of a for-of loop may not be 'async'."), + ForOfLet: _("The left-hand side of a for-of loop may not start with 'let'."), + GeneratorInSingleStatementContext: _( + "Generators can only be declared at the top level or inside a block.", + ), + + IllegalBreakContinue: _<{| type: "BreakStatement" | "ContinueStatement" |}>( + ({ type }) => + `Unsyntactic ${type === "BreakStatement" ? "break" : "continue"}.`, + ), + + IllegalLanguageModeDirective: _( + "Illegal 'use strict' directive in function with non-simple parameter list.", + ), + IllegalReturn: _("'return' outside of function."), + ImportBindingIsString: _<{| importName: string |}>( + ({ importName }) => + `A string literal cannot be used as an imported binding.\n- Did you mean \`import { "${importName}" as foo }\`?`, + ), + ImportCallArgumentTrailingComma: _( + "Trailing comma is disallowed inside import(...) arguments.", + ), + ImportCallArity: _<{| maxArgumentCount: 1 | 2 |}>( + ({ maxArgumentCount }) => + `\`import()\` requires exactly ${ + maxArgumentCount === 1 ? "one argument" : "one or two arguments" + }.`, + ), + ImportCallNotNewExpression: _("Cannot use new with import(...)."), + ImportCallSpreadArgument: _("`...` is not allowed in `import()`."), + IncompatibleRegExpUVFlags: _( + "The 'u' and 'v' regular expression flags cannot be enabled at the same time.", + ), + InvalidBigIntLiteral: _("Invalid BigIntLiteral."), + InvalidCodePoint: _("Code point out of bounds."), + InvalidCoverInitializedName: _("Invalid shorthand property initializer."), + InvalidDecimal: _("Invalid decimal."), + InvalidDigit: _<{| radix: number |}>( + ({ radix }) => `Expected number in radix ${radix}.`, + ), + InvalidEscapeSequence: _("Bad character escape sequence."), + InvalidEscapeSequenceTemplate: _("Invalid escape sequence in template."), + InvalidEscapedReservedWord: _<{| reservedWord: string |}>( + ({ reservedWord }) => `Escape sequence in keyword ${reservedWord}.`, + ), + InvalidIdentifier: _<{| identifierName: string |}>( + ({ identifierName }) => `Invalid identifier ${identifierName}.`, + ), + InvalidLhs: _<{| ancestor: LValAncestor |}>( + ({ ancestor }) => + `Invalid left-hand side in ${toNodeDescription(ancestor)}.`, + ), + InvalidLhsBinding: _<{| ancestor: LValAncestor |}>( + ({ ancestor }) => + `Binding invalid left-hand side in ${toNodeDescription(ancestor)}.`, + ), + InvalidNumber: _("Invalid number."), + InvalidOrMissingExponent: _( + "Floating-point numbers require a valid exponent after the 'e'.", + ), + InvalidOrUnexpectedToken: _<{| unexpected: string |}>( + ({ unexpected }) => `Unexpected character '${unexpected}'.`, + ), + InvalidParenthesizedAssignment: _( + "Invalid parenthesized assignment pattern.", + ), + InvalidPrivateFieldResolution: _<{| identifierName: string |}>( + ({ identifierName }) => `Private name #${identifierName} is not defined.`, + ), + InvalidPropertyBindingPattern: _("Binding member expression."), + InvalidRecordProperty: _( + "Only properties and spread elements are allowed in record definitions.", + ), + InvalidRestAssignmentPattern: _("Invalid rest operator's argument."), + LabelRedeclaration: _<{| labelName: string |}>( + ({ labelName }) => `Label '${labelName}' is already declared.`, + ), + LetInLexicalBinding: _( + "'let' is not allowed to be used as a name in 'let' or 'const' declarations.", + ), + LineTerminatorBeforeArrow: _("No line break is allowed before '=>'."), + MalformedRegExpFlags: _("Invalid regular expression flag."), + MissingClassName: _("A class name is required."), + MissingEqInAssignment: _( + "Only '=' operator can be used for specifying default value.", + ), + MissingSemicolon: _("Missing semicolon."), + MissingPlugin: _<{| missingPlugin: [string] |}>( + ({ missingPlugin }) => + `This experimental syntax requires enabling the parser plugin: ${missingPlugin + .map(name => JSON.stringify(name)) + .join(", ")}.`, + ), + // FIXME: Would be nice to make this "missingPlugins" instead. + // Also), seems like we can drop the "(s)" from the message and just make it "s". + MissingOneOfPlugins: _<{| missingPlugin: string[] |}>( + ({ missingPlugin }) => + `This experimental syntax requires enabling one of the following parser plugin(s): ${missingPlugin + .map(name => JSON.stringify(name)) + .join(", ")}.`, + ), + MissingUnicodeEscape: _("Expecting Unicode escape sequence \\uXXXX."), + MixingCoalesceWithLogical: _( + "Nullish coalescing operator(??) requires parens when mixing with logical operators.", + ), + ModuleAttributeDifferentFromType: _( + "The only accepted module attribute is `type`.", + ), + ModuleAttributeInvalidValue: _( + "Only string literals are allowed as module attribute values.", + ), + ModuleAttributesWithDuplicateKeys: _<{| key: string |}>( + ({ key }) => `Duplicate key "${key}" is not allowed in module attributes.`, + ), + ModuleExportNameHasLoneSurrogate: _<{| surrogateCharCode: number |}>( + ({ surrogateCharCode }) => + `An export name cannot include a lone surrogate, found '\\u${surrogateCharCode.toString( + 16, + )}'.`, + ), + ModuleExportUndefined: _<{| localName: string |}>( + ({ localName }) => `Export '${localName}' is not defined.`, + ), + MultipleDefaultsInSwitch: _("Multiple default clauses."), + NewlineAfterThrow: _("Illegal newline after throw."), + NoCatchOrFinally: _("Missing catch or finally clause."), + NumberIdentifier: _("Identifier directly after number."), + NumericSeparatorInEscapeSequence: _( + "Numeric separators are not allowed inside unicode escape sequences or hex escape sequences.", + ), + ObsoleteAwaitStar: _( + "'await*' has been removed from the async functions proposal. Use Promise.all() instead.", + ), + OptionalChainingNoNew: _( + "Constructors in/after an Optional Chain are not allowed.", + ), + OptionalChainingNoTemplate: _( + "Tagged Template Literals are not allowed in optionalChain.", + ), + OverrideOnConstructor: _( + "'override' modifier cannot appear on a constructor declaration.", + ), + ParamDupe: _("Argument name clash."), + PatternHasAccessor: _("Object pattern can't contain getter or setter."), + PatternHasMethod: _("Object pattern can't contain methods."), + PrivateInExpectedIn: _<{| identifierName: string |}>( + ({ identifierName }) => + `Private names are only allowed in property accesses (\`obj.#${identifierName}\`) or in \`in\` expressions (\`#${identifierName} in obj\`).`, + ), + PrivateNameRedeclaration: _<{| identifierName: string |}>( + ({ identifierName }) => `Duplicate private name #${identifierName}.`, + ), + RecordExpressionBarIncorrectEndSyntaxType: _( + "Record expressions ending with '|}' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + ), + RecordExpressionBarIncorrectStartSyntaxType: _( + "Record expressions starting with '{|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + ), + RecordExpressionHashIncorrectStartSyntaxType: _( + "Record expressions starting with '#{' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'.", + ), + RecordNoProto: _("'__proto__' is not allowed in Record expressions."), + RestTrailingComma: _("Unexpected trailing comma after rest element."), + SloppyFunction: _( + "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", + ), + StaticPrototype: _("Classes may not have static property named prototype."), + SuperNotAllowed: _( + "`super()` is only valid inside a class constructor of a subclass. Maybe a typo in the method name ('constructor') or not extending another class?", + ), + SuperPrivateField: _("Private fields can't be accessed on super."), + TrailingDecorator: _("Decorators must be attached to a class element."), + TupleExpressionBarIncorrectEndSyntaxType: _( + "Tuple expressions ending with '|]' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + ), + TupleExpressionBarIncorrectStartSyntaxType: _( + "Tuple expressions starting with '[|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + ), + TupleExpressionHashIncorrectStartSyntaxType: _( + "Tuple expressions starting with '#[' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'.", + ), + UnexpectedArgumentPlaceholder: _("Unexpected argument placeholder."), + UnexpectedAwaitAfterPipelineBody: _( + 'Unexpected "await" after pipeline body; await must have parentheses in minimal proposal.', + ), + UnexpectedDigitAfterHash: _("Unexpected digit after hash token."), + UnexpectedImportExport: _( + "'import' and 'export' may only appear at the top level.", + ), + UnexpectedKeyword: _<{| keyword: string |}>( + ({ keyword }) => `Unexpected keyword '${keyword}'.`, + ), + UnexpectedLeadingDecorator: _( + "Leading decorators must be attached to a class declaration.", + ), + UnexpectedLexicalDeclaration: _( + "Lexical declaration cannot appear in a single-statement context.", + ), + UnexpectedNewTarget: _( + "`new.target` can only be used in functions or class properties.", + ), + UnexpectedNumericSeparator: _( + "A numeric separator is only allowed between two digits.", + ), + UnexpectedPrivateField: _("Unexpected private name."), + UnexpectedReservedWord: _<{| reservedWord: string |}>( + ({ reservedWord }) => `Unexpected reserved word '${reservedWord}'.`, + ), + UnexpectedSuper: _("'super' is only allowed in object methods and classes."), + UnexpectedToken: _<{| + expected?: ?string, + unexpected?: ?string, + |}>( + ({ expected, unexpected }) => + `Unexpected token${unexpected ? ` '${unexpected}'.` : ""}${ + expected ? `, expected "${expected}"` : "" + }`, + ), + UnexpectedTokenUnaryExponentiation: _( + "Illegal expression. Wrap left hand side or entire exponentiation in parentheses.", + ), + UnsupportedBind: _("Binding should be performed on object property."), + UnsupportedDecoratorExport: _( + "A decorated export must export a class declaration.", + ), + UnsupportedDefaultExport: _( + "Only expressions, functions or classes are allowed as the `default` export.", + ), + UnsupportedImport: _( + "`import` can only be used in `import()` or `import.meta`.", + ), + UnsupportedMetaProperty: _<{| + target: string, + onlyValidPropertyName: string, + |}>( + ({ target, onlyValidPropertyName }) => + `The only valid meta property for ${target} is ${target}.${onlyValidPropertyName}.`, + ), + UnsupportedParameterDecorator: _( + "Decorators cannot be used to decorate parameters.", + ), + UnsupportedPropertyDecorator: _( + "Decorators cannot be used to decorate object literal properties.", + ), + UnsupportedSuper: _( + "'super' can only be used with function calls (i.e. super()) or in property accesses (i.e. super.prop or super[prop]).", + ), + UnterminatedComment: _("Unterminated comment."), + UnterminatedRegExp: _("Unterminated regular expression."), + UnterminatedString: _("Unterminated string constant."), + UnterminatedTemplate: _("Unterminated template."), + VarRedeclaration: _<{| identifierName: string |}>( + ({ identifierName }) => + `Identifier '${identifierName}' has already been declared.`, + ), + YieldBindingIdentifier: _( + "Can not use 'yield' as identifier inside a generator.", + ), + YieldInParameter: _("Yield expression is not allowed in formal parameters."), + ZeroDigitNumericSeparator: _( + "Numeric separator can not be used after leading 0.", + ), +}); diff --git a/packages/babel-parser/src/parse-error/strict-mode-errors.js b/packages/babel-parser/src/parse-error/strict-mode-errors.js new file mode 100644 index 000000000000..62d4fe9038f2 --- /dev/null +++ b/packages/babel-parser/src/parse-error/strict-mode-errors.js @@ -0,0 +1,35 @@ +// @flow + +import { toParseErrorCredentials } from "../parse-error"; + +export default (_: typeof toParseErrorCredentials) => ({ + StrictDelete: _("Deleting local variable in strict mode."), + + // `referenceName` is the StringValue[1] of an IdentifierReference[2], which + // is represented as just an `Identifier`[3] in the Babel AST. + // 1. https://tc39.es/ecma262/#sec-static-semantics-stringvalue + // 2. https://tc39.es/ecma262/#prod-IdentifierReference + // 3. https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md#identifier + StrictEvalArguments: _<{| referenceName: string |}>( + ({ referenceName }) => `Assigning to '${referenceName}' in strict mode.`, + ), + // `bindingName` is the StringValue[1] of a BindingIdentifier[2], which is + // represented as just an `Identifier`[3] in the Babel AST. + // 1. https://tc39.es/ecma262/#sec-static-semantics-stringvalue + // 2. https://tc39.es/ecma262/#prod-BindingIdentifier + // 3. https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md#identifier + StrictEvalArgumentsBinding: _<{| bindingName: string |}>( + ({ bindingName }) => `Binding '${bindingName}' in strict mode.`, + ), + + StrictFunction: _( + "In strict mode code, functions can only be declared at top level or inside a block.", + ), + StrictNumericEscape: _( + "The only valid numeric escape in strict mode is '\\0'.", + ), + StrictOctalLiteral: _( + "Legacy octal literals are not allowed in strict mode.", + ), + StrictWith: _("'with' in strict mode."), +}); diff --git a/packages/babel-parser/src/parse-error/to-node-description.js b/packages/babel-parser/src/parse-error/to-node-description.js new file mode 100644 index 000000000000..c604e30454b1 --- /dev/null +++ b/packages/babel-parser/src/parse-error/to-node-description.js @@ -0,0 +1,36 @@ +const NodeDescriptions = { + ArrayPattern: "array destructuring pattern", + AssignmentExpression: "assignment expression", + AssignmentPattern: "assignment expression", + ArrowFunctionExpression: "arrow function expression", + ConditionalExpression: "conditional expression", + ForOfStatement: "for-of statement", + ForInStatement: "for-in statement", + ForStatement: "for-loop", + FormalParameters: "function parameter list", + Identifier: "identifier", + ObjectPattern: "object destructuring pattern", + ParenthesizedExpression: "parenthesized expression", + RestElement: "rest element", + UpdateExpression: { + true: "prefix operation", + false: "postfix operation", + }, + VariableDeclarator: "variable declaration", + YieldExpression: "yield expression", +}; + +type NodeTypesWithDescriptions = $Keys< + $Diff, +>; +type NodeWithDescription = + | { type: "UpdateExpression", prefix: boolean } + | { type: NodeTypesWithDescriptions }; + +// eslint-disable-next-line no-confusing-arrow +const toNodeDescription = ({ type, prefix }: NodeWithDescription) => + type === "UpdateExpression" + ? NodeDescriptions.UpdateExpression[String(prefix)] + : NodeDescriptions[type]; + +export default toNodeDescription; diff --git a/packages/babel-parser/src/parser/error-codes.js b/packages/babel-parser/src/parser/error-codes.js deleted file mode 100644 index 4e89302aae11..000000000000 --- a/packages/babel-parser/src/parser/error-codes.js +++ /dev/null @@ -1,8 +0,0 @@ -// @flow - -export const ErrorCodes = Object.freeze({ - SyntaxError: "BABEL_PARSER_SYNTAX_ERROR", - SourceTypeModuleError: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED", -}); - -export type ErrorCode = $Values; diff --git a/packages/babel-parser/src/parser/error-message.js b/packages/babel-parser/src/parser/error-message.js deleted file mode 100644 index 3cdd01309785..000000000000 --- a/packages/babel-parser/src/parser/error-message.js +++ /dev/null @@ -1,259 +0,0 @@ -// @flow - -import { makeErrorTemplates, ErrorCodes } from "./error"; - -/* eslint sort-keys: "error" */ - -/** - * @module parser/error-message - */ - -// The Errors key follows https://cs.chromium.org/chromium/src/v8/src/common/message-template.h unless it does not exist -export const ErrorMessages = makeErrorTemplates( - { - AccessorIsGenerator: "A %0ter cannot be a generator.", - ArgumentsInClass: - "'arguments' is only allowed in functions and class methods.", - AsyncFunctionInSingleStatementContext: - "Async functions can only be declared at the top level or inside a block.", - AwaitBindingIdentifier: - "Can not use 'await' as identifier inside an async function.", - AwaitBindingIdentifierInStaticBlock: - "Can not use 'await' as identifier inside a static block.", - AwaitExpressionFormalParameter: - "'await' is not allowed in async function parameters.", - AwaitNotInAsyncContext: - "'await' is only allowed within async functions and at the top levels of modules.", - AwaitNotInAsyncFunction: "'await' is only allowed within async functions.", - BadGetterArity: "A 'get' accesor must not have any formal parameters.", - BadSetterArity: "A 'set' accesor must have exactly one formal parameter.", - BadSetterRestParameter: - "A 'set' accesor function argument must not be a rest parameter.", - ConstructorClassField: "Classes may not have a field named 'constructor'.", - ConstructorClassPrivateField: - "Classes may not have a private field named '#constructor'.", - ConstructorIsAccessor: "Class constructor may not be an accessor.", - ConstructorIsAsync: "Constructor can't be an async function.", - ConstructorIsGenerator: "Constructor can't be a generator.", - DeclarationMissingInitializer: "'%0' require an initialization value.", - DecoratorBeforeExport: - "Decorators must be placed *before* the 'export' keyword. You can set the 'decoratorsBeforeExport' option to false to use the 'export @decorator class {}' syntax.", - DecoratorConstructor: - "Decorators can't be used with a constructor. Did you mean '@dec class { ... }'?", - DecoratorExportClass: - "Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.", - DecoratorSemicolon: "Decorators must not be followed by a semicolon.", - DecoratorStaticBlock: "Decorators can't be used with a static block.", - DeletePrivateField: "Deleting a private field is not allowed.", - DestructureNamedImport: - "ES2015 named imports do not destructure. Use another statement for destructuring after the import.", - DuplicateConstructor: "Duplicate constructor in the same class.", - DuplicateDefaultExport: "Only one default export allowed per module.", - DuplicateExport: - "`%0` has already been exported. Exported identifiers must be unique.", - DuplicateProto: "Redefinition of __proto__ property.", - DuplicateRegExpFlags: "Duplicate regular expression flag.", - ElementAfterRest: "Rest element must be last element.", - EscapedCharNotAnIdentifier: "Invalid Unicode escape.", - ExportBindingIsString: - "A string literal cannot be used as an exported binding without `from`.\n- Did you mean `export { '%0' as '%1' } from 'some-module'`?", - ExportDefaultFromAsIdentifier: - "'from' is not allowed as an identifier after 'export default'.", - ForInOfLoopInitializer: - "'%0' loop variable declaration may not have an initializer.", - ForOfAsync: "The left-hand side of a for-of loop may not be 'async'.", - ForOfLet: "The left-hand side of a for-of loop may not start with 'let'.", - GeneratorInSingleStatementContext: - "Generators can only be declared at the top level or inside a block.", - IllegalBreakContinue: "Unsyntactic %0.", - IllegalLanguageModeDirective: - "Illegal 'use strict' directive in function with non-simple parameter list.", - IllegalReturn: "'return' outside of function.", - ImportBindingIsString: - 'A string literal cannot be used as an imported binding.\n- Did you mean `import { "%0" as foo }`?', - ImportCallArgumentTrailingComma: - "Trailing comma is disallowed inside import(...) arguments.", - ImportCallArity: "`import()` requires exactly %0.", - ImportCallNotNewExpression: "Cannot use new with import(...).", - ImportCallSpreadArgument: "`...` is not allowed in `import()`.", - IncompatibleRegExpUVFlags: - "The 'u' and 'v' regular expression flags cannot be enabled at the same time.", - InvalidBigIntLiteral: "Invalid BigIntLiteral.", - InvalidCodePoint: "Code point out of bounds.", - InvalidCoverInitializedName: "Invalid shorthand property initializer.", - InvalidDecimal: "Invalid decimal.", - InvalidDigit: "Expected number in radix %0.", - InvalidEscapeSequence: "Bad character escape sequence.", - InvalidEscapeSequenceTemplate: "Invalid escape sequence in template.", - InvalidEscapedReservedWord: "Escape sequence in keyword %0.", - InvalidIdentifier: "Invalid identifier %0.", - InvalidLhs: "Invalid left-hand side in %0.", - InvalidLhsBinding: "Binding invalid left-hand side in %0.", - InvalidNumber: "Invalid number.", - InvalidOrMissingExponent: - "Floating-point numbers require a valid exponent after the 'e'.", - InvalidOrUnexpectedToken: "Unexpected character '%0'.", - InvalidParenthesizedAssignment: "Invalid parenthesized assignment pattern.", - InvalidPrivateFieldResolution: "Private name #%0 is not defined.", - InvalidPropertyBindingPattern: "Binding member expression.", - InvalidRecordProperty: - "Only properties and spread elements are allowed in record definitions.", - InvalidRestAssignmentPattern: "Invalid rest operator's argument.", - LabelRedeclaration: "Label '%0' is already declared.", - LetInLexicalBinding: - "'let' is not allowed to be used as a name in 'let' or 'const' declarations.", - LineTerminatorBeforeArrow: "No line break is allowed before '=>'.", - MalformedRegExpFlags: "Invalid regular expression flag.", - MissingClassName: "A class name is required.", - MissingEqInAssignment: - "Only '=' operator can be used for specifying default value.", - MissingSemicolon: "Missing semicolon.", - MissingUnicodeEscape: "Expecting Unicode escape sequence \\uXXXX.", - MixingCoalesceWithLogical: - "Nullish coalescing operator(??) requires parens when mixing with logical operators.", - ModuleAttributeDifferentFromType: - "The only accepted module attribute is `type`.", - ModuleAttributeInvalidValue: - "Only string literals are allowed as module attribute values.", - ModuleAttributesWithDuplicateKeys: - 'Duplicate key "%0" is not allowed in module attributes.', - ModuleExportNameHasLoneSurrogate: - "An export name cannot include a lone surrogate, found '\\u%0'.", - ModuleExportUndefined: "Export '%0' is not defined.", - MultipleDefaultsInSwitch: "Multiple default clauses.", - NewlineAfterThrow: "Illegal newline after throw.", - NoCatchOrFinally: "Missing catch or finally clause.", - NumberIdentifier: "Identifier directly after number.", - NumericSeparatorInEscapeSequence: - "Numeric separators are not allowed inside unicode escape sequences or hex escape sequences.", - ObsoleteAwaitStar: - "'await*' has been removed from the async functions proposal. Use Promise.all() instead.", - OptionalChainingNoNew: - "Constructors in/after an Optional Chain are not allowed.", - OptionalChainingNoTemplate: - "Tagged Template Literals are not allowed in optionalChain.", - OverrideOnConstructor: - "'override' modifier cannot appear on a constructor declaration.", - ParamDupe: "Argument name clash.", - PatternHasAccessor: "Object pattern can't contain getter or setter.", - PatternHasMethod: "Object pattern can't contain methods.", - // This error is only used by the smart-mix proposal - PipeBodyIsTighter: - "Unexpected %0 after pipeline body; any %0 expression acting as Hack-style pipe body must be parenthesized due to its loose operator precedence.", - PipeTopicRequiresHackPipes: - 'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.', - PipeTopicUnbound: - "Topic reference is unbound; it must be inside a pipe body.", - PipeTopicUnconfiguredToken: - 'Invalid topic token %0. In order to use %0 as a topic reference, the pipelineOperator plugin must be configured with { "proposal": "hack", "topicToken": "%0" }.', - PipeTopicUnused: - "Hack-style pipe body does not contain a topic reference; Hack-style pipes must use topic at least once.", - PipeUnparenthesizedBody: - "Hack-style pipe body cannot be an unparenthesized %0 expression; please wrap it in parentheses.", - - // Messages whose codes start with “Pipeline” or “PrimaryTopic” - // are retained for backwards compatibility - // with the deprecated smart-mix pipe operator proposal plugin. - // They are subject to removal in a future major version. - PipelineBodyNoArrow: - 'Unexpected arrow "=>" after pipeline body; arrow function in pipeline body must be parenthesized.', - PipelineBodySequenceExpression: - "Pipeline body may not be a comma-separated sequence expression.", - PipelineHeadSequenceExpression: - "Pipeline head should not be a comma-separated sequence expression.", - PipelineTopicUnused: - "Pipeline is in topic style but does not use topic reference.", - PrimaryTopicNotAllowed: - "Topic reference was used in a lexical context without topic binding.", - PrimaryTopicRequiresSmartPipeline: - 'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.', - - PrivateInExpectedIn: - "Private names are only allowed in property accesses (`obj.#%0`) or in `in` expressions (`#%0 in obj`).", - PrivateNameRedeclaration: "Duplicate private name #%0.", - RecordExpressionBarIncorrectEndSyntaxType: - "Record expressions ending with '|}' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", - RecordExpressionBarIncorrectStartSyntaxType: - "Record expressions starting with '{|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", - RecordExpressionHashIncorrectStartSyntaxType: - "Record expressions starting with '#{' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'.", - RecordNoProto: "'__proto__' is not allowed in Record expressions.", - RestTrailingComma: "Unexpected trailing comma after rest element.", - SloppyFunction: - "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", - StaticPrototype: "Classes may not have static property named prototype.", - StrictDelete: "Deleting local variable in strict mode.", - StrictEvalArguments: "Assigning to '%0' in strict mode.", - StrictEvalArgumentsBinding: "Binding '%0' in strict mode.", - StrictFunction: - "In strict mode code, functions can only be declared at top level or inside a block.", - StrictNumericEscape: - "The only valid numeric escape in strict mode is '\\0'.", - StrictOctalLiteral: "Legacy octal literals are not allowed in strict mode.", - StrictWith: "'with' in strict mode.", - SuperNotAllowed: - "`super()` is only valid inside a class constructor of a subclass. Maybe a typo in the method name ('constructor') or not extending another class?", - SuperPrivateField: "Private fields can't be accessed on super.", - TrailingDecorator: "Decorators must be attached to a class element.", - TupleExpressionBarIncorrectEndSyntaxType: - "Tuple expressions ending with '|]' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", - TupleExpressionBarIncorrectStartSyntaxType: - "Tuple expressions starting with '[|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", - TupleExpressionHashIncorrectStartSyntaxType: - "Tuple expressions starting with '#[' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'.", - UnexpectedArgumentPlaceholder: "Unexpected argument placeholder.", - UnexpectedAwaitAfterPipelineBody: - 'Unexpected "await" after pipeline body; await must have parentheses in minimal proposal.', - UnexpectedDigitAfterHash: "Unexpected digit after hash token.", - UnexpectedImportExport: - "'import' and 'export' may only appear at the top level.", - UnexpectedKeyword: "Unexpected keyword '%0'.", - UnexpectedLeadingDecorator: - "Leading decorators must be attached to a class declaration.", - UnexpectedLexicalDeclaration: - "Lexical declaration cannot appear in a single-statement context.", - UnexpectedNewTarget: - "`new.target` can only be used in functions or class properties.", - UnexpectedNumericSeparator: - "A numeric separator is only allowed between two digits.", - UnexpectedPrivateField: "Unexpected private name.", - UnexpectedReservedWord: "Unexpected reserved word '%0'.", - UnexpectedSuper: "'super' is only allowed in object methods and classes.", - UnexpectedToken: "Unexpected token '%0'.", - UnexpectedTokenUnaryExponentiation: - "Illegal expression. Wrap left hand side or entire exponentiation in parentheses.", - UnsupportedBind: "Binding should be performed on object property.", - UnsupportedDecoratorExport: - "A decorated export must export a class declaration.", - UnsupportedDefaultExport: - "Only expressions, functions or classes are allowed as the `default` export.", - UnsupportedImport: - "`import` can only be used in `import()` or `import.meta`.", - UnsupportedMetaProperty: "The only valid meta property for %0 is %0.%1.", - UnsupportedParameterDecorator: - "Decorators cannot be used to decorate parameters.", - UnsupportedPropertyDecorator: - "Decorators cannot be used to decorate object literal properties.", - UnsupportedSuper: - "'super' can only be used with function calls (i.e. super()) or in property accesses (i.e. super.prop or super[prop]).", - UnterminatedComment: "Unterminated comment.", - UnterminatedRegExp: "Unterminated regular expression.", - UnterminatedString: "Unterminated string constant.", - UnterminatedTemplate: "Unterminated template.", - VarRedeclaration: "Identifier '%0' has already been declared.", - YieldBindingIdentifier: - "Can not use 'yield' as identifier inside a generator.", - YieldInParameter: "Yield expression is not allowed in formal parameters.", - ZeroDigitNumericSeparator: - "Numeric separator can not be used after leading 0.", - }, - /* code */ ErrorCodes.SyntaxError, -); - -export const SourceTypeModuleErrorMessages = makeErrorTemplates( - { - ImportMetaOutsideModule: `import.meta may appear only with 'sourceType: "module"'`, - ImportOutsideModule: `'import' and 'export' may appear only with 'sourceType: "module"'`, - }, - /* code */ ErrorCodes.SourceTypeModuleError, -); diff --git a/packages/babel-parser/src/parser/error.js b/packages/babel-parser/src/parser/error.js deleted file mode 100644 index db9e3de14d19..000000000000 --- a/packages/babel-parser/src/parser/error.js +++ /dev/null @@ -1,159 +0,0 @@ -// @flow -/* eslint sort-keys: "error" */ -import { type Position } from "../util/location"; -import CommentsParser from "./comments"; -import { type ErrorCode, ErrorCodes } from "./error-codes"; -import { type Node } from "../types"; - -// This function is used to raise exceptions on parse errors. It -// takes an offset integer (into the current `input`) to indicate -// the location of the error, attaches the position to the end -// of the error message, and then raises a `SyntaxError` with that -// message. - -type ErrorContext = { - pos: number, - loc: Position, - missingPlugin?: Array, - code?: string, - reasonCode?: String, -}; -export type ParsingError = SyntaxError & ErrorContext; - -export type ErrorTemplate = { - code: ErrorCode, - template: string, - reasonCode: string, -}; -export type ErrorTemplates = { - [key: string]: ErrorTemplate, -}; - -type Origin = {| node: Node |} | {| at: Position |}; - -type SyntaxPlugin = - | "flow" - | "typescript" - | "jsx" - | "placeholders" - | typeof undefined; - -function keepReasonCodeCompat(reasonCode: string, syntaxPlugin: SyntaxPlugin) { - if (!process.env.BABEL_8_BREAKING) { - // For consistency in TypeScript and Flow error codes - if (syntaxPlugin === "flow" && reasonCode === "PatternIsOptional") { - return "OptionalBindingPattern"; - } - } - return reasonCode; -} - -export function makeErrorTemplates( - messages: { - [key: string]: string, - }, - code: ErrorCode, - syntaxPlugin?: SyntaxPlugin, -): ErrorTemplates { - const templates: ErrorTemplates = {}; - Object.keys(messages).forEach(reasonCode => { - templates[reasonCode] = Object.freeze({ - code, - reasonCode: keepReasonCodeCompat(reasonCode, syntaxPlugin), - template: messages[reasonCode], - }); - }); - return Object.freeze(templates); -} - -export { ErrorCodes }; -export { - ErrorMessages as Errors, - SourceTypeModuleErrorMessages as SourceTypeModuleErrors, -} from "./error-message"; - -export type raiseFunction = (ErrorTemplate, Origin, ...any) => void; -export type ErrorData = {| message: ErrorTemplate, loc: Position |}; - -export default class ParserError extends CommentsParser { - // Forward-declaration: defined in tokenizer/index.js - /*:: - +isLookahead: boolean; - */ - - raise( - { code, reasonCode, template }: ErrorTemplate, - origin: Origin, - ...params: any - ): Error | empty { - return this.raiseWithData( - origin.node ? origin.node.loc.start : origin.at, - { code, reasonCode }, - template, - ...params, - ); - } - - /** - * Raise a parsing error on given position pos. If errorRecovery is true, - * it will first search current errors and overwrite the error thrown on the exact - * position before with the new error message. If errorRecovery is false, it - * fallbacks to `raise`. - * - * @param {number} pos - * @param {string} errorTemplate - * @param {...any} params - * @returns {(Error | empty)} - * @memberof ParserError - */ - raiseOverwrite( - loc: Position, - { code, template }: ErrorTemplate, - ...params: any - ): Error | empty { - const pos = loc.index; - const message = - template.replace(/%(\d+)/g, (_, i: number) => params[i]) + - ` (${loc.line}:${loc.column})`; - if (this.options.errorRecovery) { - const errors = this.state.errors; - for (let i = errors.length - 1; i >= 0; i--) { - const error = errors[i]; - if (error.pos === pos) { - return Object.assign(error, { message }); - } else if (error.pos < pos) { - break; - } - } - } - return this._raise({ code, loc, pos }, message); - } - - raiseWithData( - loc: Position, - data?: { - missingPlugin?: Array, - code?: string, - }, - errorTemplate: string, - ...params: any - ): Error | empty { - const pos = loc.index; - const message = - errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) + - ` (${loc.line}:${loc.column})`; - return this._raise(Object.assign(({ loc, pos }: Object), data), message); - } - - _raise(errorContext: ErrorContext, message: string): Error | empty { - // $FlowIgnore - const err: SyntaxError & ErrorContext = new SyntaxError(message); - Object.assign(err, errorContext); - if (this.options.errorRecovery) { - if (!this.isLookahead) this.state.errors.push(err); - return err; - } else { - throw err; - } - } -} diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index bb37f6a85094..a6318ef65905 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -67,8 +67,8 @@ import { newAsyncArrowScope, newExpressionScope, } from "../util/expression-scope"; -import { Errors, SourceTypeModuleErrors } from "./error"; -import type { ParsingError } from "./error"; +import { Errors, type ParseError } from "../parse-error"; +import { UnparenthesizedPipeBodyDescriptions } from "../parse-error/pipeline-operator-errors"; import { setInnerComments } from "./comments"; import { cloneIdentifier } from "./node"; @@ -77,13 +77,6 @@ import type { SourceType } from "../options"; declare var invariant; */ -const invalidHackPipeBodies = new Map([ - ["ArrowFunctionExpression", "arrow function"], - ["AssignmentExpression", "assignment"], - ["ConditionalExpression", "conditional"], - ["YieldExpression", "yield"], -]); - export default class ExpressionParser extends LValParser { // Forward-declaration: defined in statement.js /*:: @@ -146,7 +139,7 @@ export default class ExpressionParser extends LValParser { if (name === "__proto__") { if (isRecord) { - this.raise(Errors.RecordNoProto, { node: key }); + this.raise(Errors.RecordNoProto, { at: key }); return; } if (protoRef.used) { @@ -157,7 +150,7 @@ export default class ExpressionParser extends LValParser { refExpressionErrors.doubleProtoLoc = key.loc.start; } } else { - this.raise(Errors.DuplicateProto, { node: key }); + this.raise(Errors.DuplicateProto, { at: key }); } } @@ -262,7 +255,7 @@ export default class ExpressionParser extends LValParser { // the typescript and flow plugins. setOptionalParametersError( refExpressionErrors: ExpressionErrors, - resultError?: ParsingError, + resultError?: ParseError, ) { refExpressionErrors.optionalParametersLoc = resultError?.loc ?? this.state.startLoc; @@ -335,10 +328,12 @@ export default class ExpressionParser extends LValParser { node.left = left; } - this.checkLVal(left, "assignment expression"); this.next(); node.right = this.parseMaybeAssign(); - return this.finishNode(node, "AssignmentExpression"); + this.checkLVal(left, { + in: this.finishNode(node, "AssignmentExpression"), + }); + return node; } else if (ownExpressionErrors) { this.checkExpressionErrors(refExpressionErrors, true); } @@ -428,7 +423,10 @@ export default class ExpressionParser extends LValParser { !this.prodParam.hasIn || !this.match(tt._in) ) { - this.raise(Errors.PrivateInExpectedIn, { node: left }, value); + this.raise(Errors.PrivateInExpectedIn, { + at: left, + identifierName: value, + }); } this.classScope.usePrivateName(value, left.loc.start); @@ -514,11 +512,9 @@ export default class ExpressionParser extends LValParser { case "smart": return this.withTopicBindingContext(() => { if (this.prodParam.hasYield && this.isContextual(tt._yield)) { - throw this.raise( - Errors.PipeBodyIsTighter, - { at: this.state.startLoc }, - this.state.value, - ); + throw this.raise(Errors.PipeBodyIsTighter, { + at: this.state.startLoc, + }); } return this.parseSmartPipelineBodyInStyle( this.parseExprOpBaseRightExpr(op, prec), @@ -557,14 +553,16 @@ export default class ExpressionParser extends LValParser { parseHackPipeBody(): N.Expression { const { startLoc } = this.state; const body = this.parseMaybeAssign(); + const requiredParentheses = UnparenthesizedPipeBodyDescriptions.has( + body.type, + ); // TODO: Check how to handle type casts in Flow and TS once they are supported - if (invalidHackPipeBodies.has(body.type) && !body.extra?.parenthesized) { - this.raise( - Errors.PipeUnparenthesizedBody, - { at: startLoc }, - invalidHackPipeBodies.get(body.type), - ); + if (requiredParentheses && !body.extra?.parenthesized) { + this.raise(Errors.PipeUnparenthesizedBody, { + at: startLoc, + type: body.type, + }); } if (!this.topicReferenceWasUsedInCurrentContext()) { // A Hack pipe body must use the topic reference at least once. @@ -577,7 +575,7 @@ export default class ExpressionParser extends LValParser { checkExponentialAfterUnary(node: N.AwaitExpression | N.UnaryExpression) { if (this.match(tt.exponent)) { this.raise(Errors.UnexpectedTokenUnaryExponentiation, { - node: node.argument, + at: node.argument, }); } } @@ -618,9 +616,9 @@ export default class ExpressionParser extends LValParser { const arg = node.argument; if (arg.type === "Identifier") { - this.raise(Errors.StrictDelete, { node }); + this.raise(Errors.StrictDelete, { at: node }); } else if (this.hasPropertyAsPrivateName(arg)) { - this.raise(Errors.DeletePrivateField, { node }); + this.raise(Errors.DeletePrivateField, { at: node }); } } @@ -638,7 +636,7 @@ export default class ExpressionParser extends LValParser { ? tokenCanStartExpression(type) : tokenCanStartExpression(type) && !this.match(tt.modulo); if (startsExpr && !this.isAmbiguousAwait()) { - this.raiseOverwrite(startLoc, Errors.AwaitNotInAsyncContext); + this.raiseOverwrite(Errors.AwaitNotInAsyncContext, { at: startLoc }); return this.parseAwait(startPos, startLoc); } } @@ -653,8 +651,10 @@ export default class ExpressionParser extends LValParser { refExpressionErrors: ?ExpressionErrors, ): N.Expression { if (update) { - this.checkLVal(node.argument, "prefix operation"); - return this.finishNode(node, "UpdateExpression"); + this.checkLVal(node.argument, { + in: this.finishNode(node, "UpdateExpression"), + }); + return node; } const startPos = this.state.start; @@ -666,9 +666,10 @@ export default class ExpressionParser extends LValParser { node.operator = this.state.value; node.prefix = false; node.argument = expr; - this.checkLVal(expr, "postfix operation"); this.next(); - expr = this.finishNode(node, "UpdateExpression"); + this.checkLVal(expr, { + in: (expr = this.finishNode(node, "UpdateExpression")), + }); } return expr; } @@ -946,18 +947,18 @@ export default class ExpressionParser extends LValParser { } } if (node.arguments.length === 0 || node.arguments.length > 2) { - this.raise( - Errors.ImportCallArity, - { node }, - this.hasPlugin("importAssertions") || + this.raise(Errors.ImportCallArity, { + at: node, + maxArgumentCount: + this.hasPlugin("importAssertions") || this.hasPlugin("moduleAttributes") - ? "one or two arguments" - : "one argument", - ); + ? 2 + : 1, + }); } else { for (const arg of node.arguments) { if (arg.type === "SpreadElement") { - this.raise(Errors.ImportCallSpreadArgument, { node: arg }); + this.raise(Errors.ImportCallSpreadArgument, { at: arg }); } } } @@ -1182,7 +1183,7 @@ export default class ExpressionParser extends LValParser { if (callee.type === "MemberExpression") { return this.finishNode(node, "BindExpression"); } else { - throw this.raise(Errors.UnsupportedBind, { node: callee }); + throw this.raise(Errors.UnsupportedBind, { at: callee }); } } @@ -1193,11 +1194,10 @@ export default class ExpressionParser extends LValParser { // We can throw a better error message and recover, rather than // just throwing "Unexpected token" (which is the default // behavior of this big switch statement). - this.raise( - Errors.PrivateInExpectedIn, - { at: this.state.startLoc }, - this.state.value, - ); + this.raise(Errors.PrivateInExpectedIn, { + at: this.state.startLoc, + identifierName: this.state.value, + }); return this.parsePrivateName(); } @@ -1422,11 +1422,10 @@ export default class ExpressionParser extends LValParser { return this.finishNode(node, nodeType); } else { // The token does not match the plugin’s configuration. - throw this.raise( - Errors.PipeTopicUnconfiguredToken, - { at: startLoc }, - tokenLabelName(tokenType), - ); + throw this.raise(Errors.PipeTopicUnconfiguredToken, { + at: startLoc, + token: tokenLabelName(tokenType), + }); } } @@ -1512,12 +1511,12 @@ export default class ExpressionParser extends LValParser { !this.scope.allowDirectSuper && !this.options.allowSuperOutsideMethod ) { - this.raise(Errors.SuperNotAllowed, { node }); + this.raise(Errors.SuperNotAllowed, { at: node }); } else if ( !this.scope.allowSuper && !this.options.allowSuperOutsideMethod ) { - this.raise(Errors.UnexpectedSuper, { node }); + this.raise(Errors.UnexpectedSuper, { at: node }); } if ( @@ -1525,7 +1524,7 @@ export default class ExpressionParser extends LValParser { !this.match(tt.bracketL) && !this.match(tt.dot) ) { - this.raise(Errors.UnsupportedSuper, { node }); + this.raise(Errors.UnsupportedSuper, { at: node }); } return this.finishNode(node, "Super"); @@ -1589,12 +1588,11 @@ export default class ExpressionParser extends LValParser { node.property = this.parseIdentifier(true); if (node.property.name !== propertyName || containsEsc) { - this.raise( - Errors.UnsupportedMetaProperty, - { node: node.property }, - meta.name, - propertyName, - ); + this.raise(Errors.UnsupportedMetaProperty, { + at: node.property, + target: meta.name, + onlyValidPropertyName: propertyName, + }); } return this.finishNode(node, "MetaProperty"); @@ -1607,9 +1605,7 @@ export default class ExpressionParser extends LValParser { if (this.isContextual(tt._meta)) { if (!this.inModule) { - this.raise(SourceTypeModuleErrors.ImportMetaOutsideModule, { - node: id, - }); + this.raise(Errors.ImportMetaOutsideModule, { at: id }); } this.sawUnambiguousESM = true; } @@ -1822,7 +1818,7 @@ export default class ExpressionParser extends LValParser { const metaProp = this.parseMetaProperty(node, meta, "target"); if (!this.scope.inNonArrowFunction && !this.scope.inClass) { - this.raise(Errors.UnexpectedNewTarget, { node: metaProp }); + this.raise(Errors.UnexpectedNewTarget, { at: metaProp }); } return metaProp; @@ -1840,7 +1836,7 @@ export default class ExpressionParser extends LValParser { parseNew(node: N.Expression): N.NewExpression { node.callee = this.parseNoCallExpr(); if (node.callee.type === "Import") { - this.raise(Errors.ImportCallNotNewExpression, { node: node.callee }); + this.raise(Errors.ImportCallNotNewExpression, { at: node.callee }); } else if (this.isOptionalChain(node.callee)) { this.raise(Errors.OptionalChainingNoNew, { at: this.state.lastTokEndLoc, @@ -1964,7 +1960,7 @@ export default class ExpressionParser extends LValParser { !this.isObjectProperty(prop) && prop.type !== "SpreadElement" ) { - this.raise(Errors.InvalidRecordProperty, { node: prop }); + this.raise(Errors.InvalidRecordProperty, { at: prop }); } // $FlowIgnore @@ -2070,11 +2066,10 @@ export default class ExpressionParser extends LValParser { prop.kind = keyName; if (this.match(tt.star)) { isGenerator = true; - this.raise( - Errors.AccessorIsGenerator, - { at: this.state.curPosition() }, - keyName, - ); + this.raise(Errors.AccessorIsGenerator, { + at: this.state.curPosition(), + kind: keyName, + }); this.next(); } this.parsePropertyName(prop); @@ -2115,7 +2110,7 @@ export default class ExpressionParser extends LValParser { if (params.length !== paramCount) { this.raise( method.kind === "get" ? Errors.BadGetterArity : Errors.BadSetterArity, - { node: method }, + { at: method }, ); } @@ -2123,7 +2118,7 @@ export default class ExpressionParser extends LValParser { method.kind === "set" && params[params.length - 1]?.type === "RestElement" ) { - this.raise(Errors.BadSetterRestParameter, { node: method }); + this.raise(Errors.BadSetterRestParameter, { at: method }); } } @@ -2467,15 +2462,15 @@ export default class ExpressionParser extends LValParser { if (hasStrictModeDirective && nonSimple) { // This logic is here to align the error location with the ESTree plugin. - const errorOrigin = - // $FlowIgnore - (node.kind === "method" || node.kind === "constructor") && - // $FlowIgnore - !!node.key - ? { at: node.key.loc.end } - : { node }; - - this.raise(Errors.IllegalLanguageModeDirective, errorOrigin); + this.raise(Errors.IllegalLanguageModeDirective, { + at: + // $FlowIgnore + (node.kind === "method" || node.kind === "constructor") && + // $FlowIgnore + !!node.key + ? node.key.loc.end + : node, + }); } const strictModeChanged = !oldStrict && this.state.strict; @@ -2491,14 +2486,7 @@ export default class ExpressionParser extends LValParser { // 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, - "function name", - BIND_OUTSIDE, - undefined, - undefined, - strictModeChanged, - ); + this.checkIdentifier(node.id, BIND_OUTSIDE, strictModeChanged); } }, ); @@ -2508,11 +2496,15 @@ export default class ExpressionParser extends LValParser { this.expressionScope.exit(); } + isSimpleParameter(node: N.Pattern | N.TSParameterProperty) { + return node.type === "Identifier"; + } + isSimpleParamList( params: $ReadOnlyArray, ): boolean { for (let i = 0, len = params.length; i < len; i++) { - if (params[i].type !== "Identifier") return false; + if (!this.isSimpleParameter(params[i])) return false; } return true; } @@ -2524,16 +2516,21 @@ export default class ExpressionParser extends LValParser { isArrowFunction: ?boolean, strictModeChanged?: boolean = true, ): void { - const checkClashes = new Set(); + const checkClashes = !allowDuplicates && new Set(); + // We create a fake node with the "ephemeral" type `FormalParameters`[1] + // since we just store an array of parameters. Perhaps someday we can have + // something like class FormalParameters extends Array { ... }, which would + // also be helpful when traversing this node. + // + // 1. https://tc39.es/ecma262/#prod-FormalParameters + const formalParameters = { type: "FormalParameters" }; for (const param of node.params) { - this.checkLVal( - param, - "function parameter list", - BIND_VAR, - allowDuplicates ? null : checkClashes, - undefined, + this.checkLVal(param, { + in: formalParameters, + binding: BIND_VAR, + checkClashes, strictModeChanged, - ); + }); } } @@ -2579,11 +2576,10 @@ export default class ExpressionParser extends LValParser { let elt; if (this.match(tt.comma)) { if (!allowEmpty) { - this.raise( - Errors.UnexpectedToken, - { at: this.state.curPosition() }, - ",", - ); + this.raise(Errors.UnexpectedToken, { + at: this.state.curPosition(), + unexpected: ",", + }); } elt = null; } else if (this.match(tt.ellipsis)) { @@ -2696,10 +2692,7 @@ export default class ExpressionParser extends LValParser { return; } - this.expressionScope.recordAsyncArrowParametersError( - Errors.AwaitBindingIdentifier, - startLoc, - ); + this.expressionScope.recordAsyncArrowParametersError({ at: startLoc }); } else if (word === "arguments") { if (this.scope.inClassAndNotInNonArrowFunction) { this.raise(Errors.ArgumentsInClass, { at: startLoc }); @@ -2708,7 +2701,10 @@ export default class ExpressionParser extends LValParser { } if (checkKeywords && isKeyword(word)) { - this.raise(Errors.UnexpectedKeyword, { at: startLoc }, word); + this.raise(Errors.UnexpectedKeyword, { + at: startLoc, + keyword: word, + }); return; } @@ -2719,7 +2715,10 @@ export default class ExpressionParser extends LValParser { : isStrictReservedWord; if (reservedTest(word, this.inModule)) { - this.raise(Errors.UnexpectedReservedWord, { at: startLoc }, word); + this.raise(Errors.UnexpectedReservedWord, { + at: startLoc, + reservedWord: word, + }); } } @@ -2737,12 +2736,12 @@ export default class ExpressionParser extends LValParser { const node = this.startNodeAt(startPos, startLoc); this.expressionScope.recordParameterInitializerError( - node.loc.start, Errors.AwaitExpressionFormalParameter, + { at: node }, ); if (this.eat(tt.star)) { - this.raise(Errors.ObsoleteAwaitStar, { node }); + this.raise(Errors.ObsoleteAwaitStar, { at: node }); } if (!this.scope.inFunction && !this.options.allowAwaitOutsideFunction) { @@ -2786,8 +2785,8 @@ export default class ExpressionParser extends LValParser { const node = this.startNode(); this.expressionScope.recordParameterInitializerError( - node.loc.start, Errors.YieldInParameter, + { at: node }, ); this.next(); diff --git a/packages/babel-parser/src/parser/lval.js b/packages/babel-parser/src/parser/lval.js index cc867177009d..aeecc15515a1 100644 --- a/packages/babel-parser/src/parser/lval.js +++ b/packages/babel-parser/src/parser/lval.js @@ -7,15 +7,15 @@ import type { TSParameterProperty, Decorator, Expression, + Identifier, Node, Pattern, RestElement, SpreadElement, /*:: ObjectOrClassMember, */ /*:: ClassMember, */ - /*:: ObjectMember, */ + ObjectMember, /*:: TsNamedTypeElementBase, */ - /*:: Identifier, */ /*:: PrivateName, */ /*:: ObjectExpression, */ /*:: ObjectPattern, */ @@ -26,9 +26,16 @@ import { isStrictBindReservedWord, } from "../util/identifier"; import { NodeUtils } from "./node"; -import { type BindingTypes, BIND_NONE } from "../util/scopeflags"; +import { + type BindingTypes, + BIND_NONE, + BIND_SCOPE_LEXICAL, +} from "../util/scopeflags"; import { ExpressionErrors } from "./util"; -import { Errors } from "./error"; +import { Errors, type LValAncestor } from "../parse-error"; + +const getOwn = (object, key) => + Object.hasOwnProperty.call(object, key) && object[key]; const unwrapParenthesizedExpression = (node: Node): Node => { return node.type === "ParenthesizedExpression" @@ -99,18 +106,15 @@ export default class LValParser extends NodeUtils { // i.e. `([(a) = []] = []) => {}` // see also `recordParenthesizedIdentifierError` signature in packages/babel-parser/src/util/expression-scope.js if (parenthesized.type === "Identifier") { - this.expressionScope.recordParenthesizedIdentifierError( - Errors.InvalidParenthesizedAssignment, - node.loc.start, - ); + this.expressionScope.recordParenthesizedIdentifierError({ at: node }); } else if (parenthesized.type !== "MemberExpression") { // A parenthesized member expression can be in LHS but not in pattern. // If the LHS is later interpreted as a pattern, `checkLVal` will throw for member expression binding // i.e. `([(a.b) = []] = []) => {}` - this.raise(Errors.InvalidParenthesizedAssignment, { node }); + this.raise(Errors.InvalidParenthesizedAssignment, { at: node }); } } else { - this.raise(Errors.InvalidParenthesizedAssignment, { node }); + this.raise(Errors.InvalidParenthesizedAssignment, { at: node }); } } @@ -203,16 +207,14 @@ export default class LValParser extends NodeUtils { isLHS: boolean, ) { if (prop.type === "ObjectMethod") { - /* eslint-disable @babel/development-internal/dry-error-messages */ this.raise( prop.kind === "get" || prop.kind === "set" ? Errors.PatternHasAccessor : Errors.PatternHasMethod, - { node: prop.key }, + { at: prop.key }, ); - /* eslint-enable @babel/development-internal/dry-error-messages */ } else if (prop.type === "SpreadElement" && !isLast) { - this.raise(Errors.RestTrailingComma, { node: prop }); + this.raise(Errors.RestTrailingComma, { at: prop }); } else { this.toAssignable(prop, isLHS); } @@ -256,7 +258,7 @@ export default class LValParser extends NodeUtils { if (elt) { this.toAssignable(elt, isLHS); if (elt.type === "RestElement") { - this.raise(Errors.RestTrailingComma, { node: elt }); + this.raise(Errors.RestTrailingComma, { at: elt }); } } } @@ -494,145 +496,215 @@ export default class LValParser extends NodeUtils { node.right = this.parseMaybeAssignAllowIn(); return this.finishNode(node, "AssignmentPattern"); } + /** + * Return information use in determining whether a Node of a given type is an LVal, + * possibly given certain additional context information. + * + * Subclasser notes: This method has kind of a lot of mixed, but related, + * responsibilities. If we can definitively determine with the information + * provided that this either *is* or *isn't* a valid `LVal`, then the return + * value is easy: just return `true` or `false`. However, if it is a valid + * LVal *ancestor*, and thus it's descendents must be subsquently visited to + * continue the "investigation", then this method should return the relevant + * child key as a `string`. In some special cases, you additionally want to + * convey that this node should be treated as if it were parenthesized. In + * that case, a tuple of [key: string, parenthesized: boolean] is returned. + * The `string`-only return option is actually just a shorthand for: + * `[key: string, parenthesized: false]`. + * + * @param {NodeType} type A Node `type` string + * @param {boolean} isParenthesized + * Whether the node in question is parenthesized. + * @param {BindingTypes} binding + * The binding operation that is being considered for this potential + * LVal. + * @returns { boolean | string | [string, boolean] } + * `true` or `false` if we can immediately determine whether the node + * type in question can be treated as an `LVal`. + * A `string` key to traverse if we must check this child. + * A `[string, boolean]` tuple if we need to check this child and + * treat is as parenthesized. + */ + // eslint-disable-next-line no-unused-vars + isValidLVal(type: string, isParenthesized: boolean, binding: BindingTypes) { + return getOwn( + { + AssignmentPattern: "left", + RestElement: "argument", + ObjectProperty: "value", + ParenthesizedExpression: "expression", + ArrayPattern: "elements", + ObjectPattern: "properties", + }, + type, + ); + } /** - * Verify that if a node is an lval - something that can be assigned to. + * Verify that a target expression is an lval (something that can be assigned to). * - * @param {Expression} expr The given node - * @param {string} contextDescription The auxiliary context information printed when error is thrown - * @param {BindingTypes} [bindingType=BIND_NONE] The desired binding type. If the given node is an identifier and `bindingType` is not - BIND_NONE, `checkLVal` will register binding to the parser scope - See also src/util/scopeflags.js - * @param {?Set} checkClashes An optional string set to check if an identifier name is included. `checkLVal` will add checked - identifier name to `checkClashes` It is used in tracking duplicates in function parameter lists. If - it is nullish, `checkLVal` will skip duplicate checks - * @param {boolean} [disallowLetBinding] Whether an identifier named "let" should be disallowed - * @param {boolean} [strictModeChanged=false] Whether an identifier has been parsed in a sloppy context but should be reinterpreted as - strict-mode. e.g. `(arguments) => { "use strict "}` + * @param {Expression} expression The expression in question to check. + * @param {Object} options A set of options described below. + * @param {LValAncestor} options.in + * The relevant ancestor to provide context information for the error + * if the check fails. + * @param {BindingTypes} [options.binding=BIND_NONE] + * The desired binding type. If the given expression is an identifier + * and `binding` is not `BIND_NONE`, `checkLVal` will register binding + * to the parser scope See also `src/util/scopeflags.js` + * @param {Set|false} [options.checkClashes=false] + * An optional string set to check if an identifier name is included. + * `checkLVal` will add checked identifier name to `checkClashes` It is + * used in tracking duplicates in function parameter lists. If it is + * false, `checkLVal` will skip duplicate checks + * @param {boolean} [options.allowingSloppyLetBinding] + * Whether an identifier named "let" should be allowed in sloppy mode. + * Defaults to `true` unless lexical scope its being used. This property + * is only relevant if the parser's state is in sloppy mode. + * @param {boolean} [options.strictModeChanged=false] + * Whether an identifier has been parsed in a sloppy context but should + * be reinterpreted as strict-mode. e.g. `(arguments) => { "use strict "}` + * @param {boolean} [options.hasParenthesizedAncestor=false] + * This is only used internally during recursive calls, and you should + * not have to set it yourself. * @memberof LValParser */ + checkLVal( - expr: Expression, - contextDescription: string, - bindingType: BindingTypes = BIND_NONE, - checkClashes: ?Set, - disallowLetBinding?: boolean, - strictModeChanged?: boolean = false, + expression: Expression | ObjectMember | RestElement, + { + in: ancestor, + binding = BIND_NONE, + checkClashes = false, + strictModeChanged = false, + allowingSloppyLetBinding = !(binding & BIND_SCOPE_LEXICAL), + hasParenthesizedAncestor = false, + }: { + in: LValAncestor, + binding?: BindingTypes, + checkClashes?: Set | false, + strictModeChanged?: boolean, + allowingSloppyLetBinding?: boolean, + hasParenthesizedAncestor?: boolean, + }, ): void { - switch (expr.type) { - case "Identifier": { - const { name } = expr; - if ( - this.state.strict && - // "Global" reserved words have already been checked by parseIdentifier, - // unless they have been found in the id or parameters of a strict-mode - // function in a sloppy context. - (strictModeChanged - ? isStrictBindReservedWord(name, this.inModule) - : isStrictBindOnlyReservedWord(name)) - ) { - this.raise( - bindingType === BIND_NONE - ? Errors.StrictEvalArguments - : Errors.StrictEvalArgumentsBinding, - { node: expr }, - name, - ); - } + const type = expression.type; - if (checkClashes) { - if (checkClashes.has(name)) { - this.raise(Errors.ParamDupe, { node: expr }); - } else { - checkClashes.add(name); - } - } - if (disallowLetBinding && name === "let") { - this.raise(Errors.LetInLexicalBinding, { node: expr }); - } - if (!(bindingType & BIND_NONE)) { - this.scope.declareName(name, bindingType, expr.loc.start); - } - break; + // If we find here an ObjectMethod, it's because this was originally + // an ObjectExpression which has then been converted. + // toAssignable already reported this error with a nicer message. + if (this.isObjectMethod(expression)) return; + + if (type === "MemberExpression") { + if (binding !== BIND_NONE) { + this.raise(Errors.InvalidPropertyBindingPattern, { at: expression }); } + return; + } - case "MemberExpression": - if (bindingType !== BIND_NONE) { - this.raise(Errors.InvalidPropertyBindingPattern, { - node: expr, - }); - } - break; + if (expression.type === "Identifier") { + this.checkIdentifier( + expression, + binding, + strictModeChanged, + allowingSloppyLetBinding, + ); - case "ObjectPattern": - for (let prop of expr.properties) { - if (this.isObjectProperty(prop)) prop = prop.value; - // If we find here an ObjectMethod, it's because this was originally - // an ObjectExpression which has then been converted. - // toAssignable already reported this error with a nicer message. - else if (this.isObjectMethod(prop)) continue; - - this.checkLVal( - prop, - "object destructuring pattern", - bindingType, - checkClashes, - disallowLetBinding, - ); - } - break; + const { name } = expression; - case "ArrayPattern": - for (const elem of expr.elements) { - if (elem) { - this.checkLVal( - elem, - "array destructuring pattern", - bindingType, - checkClashes, - disallowLetBinding, - ); - } + if (checkClashes) { + if (checkClashes.has(name)) { + this.raise(Errors.ParamDupe, { at: expression }); + } else { + checkClashes.add(name); } - break; + } - case "AssignmentPattern": - this.checkLVal( - expr.left, - "assignment pattern", - bindingType, - checkClashes, - ); - break; + return; + } - case "RestElement": - this.checkLVal( - expr.argument, - "rest element", - bindingType, - checkClashes, - ); - break; + const validity = this.isValidLVal( + expression.type, + hasParenthesizedAncestor || expression.extra?.parenthesized, + binding, + ); - case "ParenthesizedExpression": - this.checkLVal( - expr.expression, - "parenthesized expression", - bindingType, + if (validity === true) return; + + if (validity === false) { + const ParseErrorClass = + binding === BIND_NONE ? Errors.InvalidLhs : Errors.InvalidLhsBinding; + + this.raise(ParseErrorClass, { + at: expression, + ancestor: + ancestor.type === "UpdateExpression" + ? { type: "UpdateExpression", prefix: ancestor.prefix } + : { type: ancestor.type }, + }); + return; + } + + const [key, isParenthesizedExpression] = Array.isArray(validity) + ? validity + : [validity, type === "ParenthesizedExpression"]; + const nextAncestor = + expression.type === "ArrayPattern" || + expression.type === "ObjectPattern" || + expression.type === "ParenthesizedExpression" + ? expression + : ancestor; + + // Flow has difficulty tracking `key` and `expression`, but only if we use + // null-proto objects. If we use normal objects, everything works fine. + // $FlowIgnore + for (const child of [].concat(expression[key])) { + if (child) { + this.checkLVal(child, { + in: nextAncestor, + binding, checkClashes, - ); - break; + allowingSloppyLetBinding, + strictModeChanged, + hasParenthesizedAncestor: isParenthesizedExpression, + }); + } + } + } - default: { - this.raise( - bindingType === BIND_NONE - ? Errors.InvalidLhs - : Errors.InvalidLhsBinding, - { node: expr }, - contextDescription, - ); + checkIdentifier( + at: Identifier, + bindingType: BindingTypes, + strictModeChanged: boolean = false, + allowLetBinding: boolean = !(bindingType & BIND_SCOPE_LEXICAL), + ) { + if ( + this.state.strict && + (strictModeChanged + ? isStrictBindReservedWord(at.name, this.inModule) + : isStrictBindOnlyReservedWord(at.name)) + ) { + if (bindingType === BIND_NONE) { + this.raise(Errors.StrictEvalArguments, { at, referenceName: at.name }); + } else { + this.raise(Errors.StrictEvalArgumentsBinding, { + at, + bindingName: at.name, + }); } } + + if (!allowLetBinding && at.name === "let") { + this.raise(Errors.LetInLexicalBinding, { at }); + } + + if (!(bindingType & BIND_NONE)) { + this.declareNameFromIdentifier(at, bindingType); + } + } + + declareNameFromIdentifier(identifier: Identifier, binding: BindingTypes) { + this.scope.declareName(identifier.name, binding, identifier.loc.start); } checkToRestConversion(node: SpreadElement): void { @@ -641,7 +713,7 @@ export default class LValParser extends NodeUtils { node.argument.type !== "MemberExpression" ) { this.raise(Errors.InvalidRestAssignmentPattern, { - node: node.argument, + at: node.argument, }); } } diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 0f7cbec41aad..1ca7c1a5ef04 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -10,7 +10,7 @@ import { getExportedToken, } from "../tokenizer/types"; import ExpressionParser from "./expression"; -import { Errors, SourceTypeModuleErrors } from "./error"; +import { Errors } from "../parse-error"; import { isIdentifierChar, isIdentifierStart } from "../util/identifier"; import { lineBreak } from "../util/whitespace"; import * as charCodes from "charcodes"; @@ -217,8 +217,8 @@ export default class StatementParser extends ExpressionParser { !this.options.allowUndeclaredExports && this.scope.undefinedExports.size > 0 ) { - for (const [name, loc] of Array.from(this.scope.undefinedExports)) { - this.raise(Errors.ModuleExportUndefined, { at: loc }, name); + for (const [localName, at] of Array.from(this.scope.undefinedExports)) { + this.raise(Errors.ModuleExportUndefined, { at, localName }); } } return this.finishNode(program, "Program"); @@ -480,7 +480,7 @@ export default class StatementParser extends ExpressionParser { assertModuleNodeAllowed(node: N.Node): void { if (!this.options.allowImportExportEverywhere && !this.inModule) { - this.raise(SourceTypeModuleErrors.ImportOutsideModule, { node }); + this.raise(Errors.ImportOutsideModule, { at: node }); } } @@ -608,11 +608,8 @@ export default class StatementParser extends ExpressionParser { } } if (i === this.state.labels.length) { - this.raise( - Errors.IllegalBreakContinue, - { node }, - isBreak ? "break" : "continue", - ); + const type = isBreak ? "BreakStatement" : "ContinueStatement"; + this.raise(Errors.IllegalBreakContinue, { at: node, type }); } } @@ -709,7 +706,7 @@ export default class StatementParser extends ExpressionParser { if (isForOf) { // Check for leading tokens that are forbidden in for-of loops: if (startsWithLet) { - this.raise(Errors.ForOfLet, { node: init }); + this.raise(Errors.ForOfLet, { at: init }); } if ( @@ -722,14 +719,14 @@ export default class StatementParser extends ExpressionParser { // parsed as an identifier. If it was parsed as the start of an async // arrow function (e.g. `for (async of => {} of []);`), the LVal check // further down will raise a more appropriate error. - this.raise(Errors.ForOfAsync, { node: init }); + this.raise(Errors.ForOfAsync, { at: init }); } } if (isForOf || this.match(tt._in)) { this.checkDestructuringPrivate(refExpressionErrors); this.toAssignable(init, /* isLHS */ true); - const description = isForOf ? "for-of statement" : "for-in statement"; - this.checkLVal(init, description); + const type = isForOf ? "ForOfStatement" : "ForInStatement"; + this.checkLVal(init, { in: { type } }); return this.parseForIn(node, init, awaitAt); } else { this.checkExpressionErrors(refExpressionErrors, true); @@ -844,7 +841,11 @@ export default class StatementParser extends ExpressionParser { const simple = param.type === "Identifier"; this.scope.enter(simple ? SCOPE_SIMPLE_CATCH : 0); - this.checkLVal(param, "catch clause", BIND_LEXICAL); + this.checkLVal(param, { + in: { type: "CatchClause" }, + binding: BIND_LEXICAL, + allowingSloppyLetBinding: true, + }); return param; } @@ -883,7 +884,7 @@ export default class StatementParser extends ExpressionParser { node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null; if (!node.handler && !node.finalizer) { - this.raise(Errors.NoCatchOrFinally, { node }); + this.raise(Errors.NoCatchOrFinally, { at: node }); } return this.finishNode(node, "TryStatement"); @@ -892,9 +893,10 @@ export default class StatementParser extends ExpressionParser { parseVarStatement( node: N.VariableDeclaration, kind: "var" | "let" | "const", + allowMissingInitializer: boolean = false, ): N.VariableDeclaration { this.next(); - this.parseVar(node, false, kind); + this.parseVar(node, false, kind, allowMissingInitializer); this.semicolon(); return this.finishNode(node, "VariableDeclaration"); } @@ -953,7 +955,10 @@ export default class StatementParser extends ExpressionParser { ): N.LabeledStatement { for (const label of this.state.labels) { if (label.name === maybeName) { - this.raise(Errors.LabelRedeclaration, { node: expr }, maybeName); + this.raise(Errors.LabelRedeclaration, { + at: expr, + labelName: maybeName, + }); } } @@ -1161,15 +1166,17 @@ export default class StatementParser extends ExpressionParser { init.kind !== "var" || init.declarations[0].id.type !== "Identifier") ) { - this.raise( - Errors.ForInOfLoopInitializer, - { node: init }, - isForIn ? "for-in" : "for-of", - ); + this.raise(Errors.ForInOfLoopInitializer, { + at: init, + type: isForIn ? "ForInStatement" : "ForOfStatement", + }); } if (init.type === "AssignmentPattern") { - this.raise(Errors.InvalidLhs, { node: init }, "for-loop"); + this.raise(Errors.InvalidLhs, { + at: init, + ancestor: { type: "ForStatement" }, + }); } node.left = init; @@ -1200,42 +1207,37 @@ export default class StatementParser extends ExpressionParser { node: N.VariableDeclaration, isFor: boolean, kind: "var" | "let" | "const", + allowMissingInitializer: boolean = false, ): N.VariableDeclaration { const declarations = (node.declarations = []); - const isTypescript = this.hasPlugin("typescript"); node.kind = kind; for (;;) { const decl = this.startNode(); this.parseVarId(decl, kind); - if (this.eat(tt.eq)) { - decl.init = isFor - ? this.parseMaybeAssignDisallowIn() - : this.parseMaybeAssignAllowIn(); - } else { + decl.init = !this.eat(tt.eq) + ? null + : isFor + ? this.parseMaybeAssignDisallowIn() + : this.parseMaybeAssignAllowIn(); + + if (decl.init === null && !allowMissingInitializer) { if ( - kind === "const" && - !(this.match(tt._in) || this.isContextual(tt._of)) - ) { - // `const` with no initializer is allowed in TypeScript. - // It could be a declaration like `const x: number;`. - if (!isTypescript) { - this.raise( - Errors.DeclarationMissingInitializer, - { at: this.state.lastTokEndLoc }, - "Const declarations", - ); - } - } else if ( decl.id.type !== "Identifier" && !(isFor && (this.match(tt._in) || this.isContextual(tt._of))) ) { - this.raise( - Errors.DeclarationMissingInitializer, - { at: this.state.lastTokEndLoc }, - "Complex binding patterns", - ); + this.raise(Errors.DeclarationMissingInitializer, { + at: this.state.lastTokEndLoc, + kind: "destructuring", + }); + } else if ( + kind === "const" && + !(this.match(tt._in) || this.isContextual(tt._of)) + ) { + this.raise(Errors.DeclarationMissingInitializer, { + at: this.state.lastTokEndLoc, + kind: "const", + }); } - decl.init = null; } declarations.push(this.finishNode(decl, "VariableDeclarator")); if (!this.eat(tt.comma)) break; @@ -1245,13 +1247,10 @@ export default class StatementParser extends ExpressionParser { parseVarId(decl: N.VariableDeclarator, kind: "var" | "let" | "const"): void { decl.id = this.parseBindingAtom(); - this.checkLVal( - decl.id, - "variable declaration", - kind === "var" ? BIND_VAR : BIND_LEXICAL, - undefined, - kind !== "var", - ); + this.checkLVal(decl.id, { + in: { type: "VariableDeclarator" }, + binding: kind === "var" ? BIND_VAR : BIND_LEXICAL, + }); } // Parse a function declaration or literal (depending on the @@ -1444,7 +1443,7 @@ export default class StatementParser extends ExpressionParser { member.decorators && member.decorators.length > 0 ) { - this.raise(Errors.DecoratorConstructor, { node: member }); + this.raise(Errors.DecoratorConstructor, { at: member }); } } }); @@ -1554,7 +1553,7 @@ export default class StatementParser extends ExpressionParser { if (this.isNonstaticConstructor(publicMethod)) { this.raise(Errors.ConstructorIsGenerator, { - node: publicMethod.key, + at: publicMethod.key, }); } @@ -1594,10 +1593,10 @@ export default class StatementParser extends ExpressionParser { // TypeScript allows multiple overloaded constructor declarations. if (state.hadConstructor && !this.hasPlugin("typescript")) { - this.raise(Errors.DuplicateConstructor, { node: key }); + this.raise(Errors.DuplicateConstructor, { at: key }); } if (isConstructor && this.hasPlugin("typescript") && member.override) { - this.raise(Errors.OverrideOnConstructor, { node: key }); + this.raise(Errors.OverrideOnConstructor, { at: key }); } state.hadConstructor = true; allowsDirectSuper = state.hadSuperClass; @@ -1646,7 +1645,7 @@ export default class StatementParser extends ExpressionParser { ); } else { if (this.isNonstaticConstructor(publicMethod)) { - this.raise(Errors.ConstructorIsAsync, { node: publicMethod.key }); + this.raise(Errors.ConstructorIsAsync, { at: publicMethod.key }); } this.pushClassMethod( @@ -1676,7 +1675,7 @@ export default class StatementParser extends ExpressionParser { this.pushClassPrivateMethod(classBody, privateMethod, false, false); } else { if (this.isNonstaticConstructor(publicMethod)) { - this.raise(Errors.ConstructorIsAccessor, { node: publicMethod.key }); + this.raise(Errors.ConstructorIsAccessor, { at: publicMethod.key }); } this.pushClassMethod( classBody, @@ -1757,7 +1756,7 @@ export default class StatementParser extends ExpressionParser { this.state.labels = oldLabels; classBody.body.push(this.finishNode(member, "StaticBlock")); if (member.decorators?.length) { - this.raise(Errors.DecoratorStaticBlock, { node: member }); + this.raise(Errors.DecoratorStaticBlock, { at: member }); } } @@ -1768,7 +1767,7 @@ export default class StatementParser extends ExpressionParser { ) { // Non-computed field, which is either an identifier named "constructor" // or a string literal named "constructor" - this.raise(Errors.ConstructorClassField, { node: prop.key }); + this.raise(Errors.ConstructorClassField, { at: prop.key }); } classBody.body.push(this.parseClassProperty(prop)); @@ -1800,7 +1799,7 @@ export default class StatementParser extends ExpressionParser { if (key.name === "constructor" || key.value === "constructor") { // Non-computed field, which is either an identifier named "constructor" // or a string literal named "constructor" - this.raise(Errors.ConstructorClassField, { node: key }); + this.raise(Errors.ConstructorClassField, { at: key }); } } @@ -1930,7 +1929,7 @@ export default class StatementParser extends ExpressionParser { if (tokenIsIdentifier(this.state.type)) { node.id = this.parseIdentifier(); if (isStatement) { - this.checkLVal(node.id, "class name", bindingType); + this.declareNameFromIdentifier(node.id, bindingType); } } else { if (optionalId || !isStatement) { @@ -2233,7 +2232,7 @@ export default class StatementParser extends ExpressionParser { !declaration.extra?.parenthesized ) { this.raise(Errors.ExportDefaultFromAsIdentifier, { - node: declaration, + at: declaration, }); } } @@ -2241,19 +2240,18 @@ export default class StatementParser extends ExpressionParser { // Named exports for (const specifier of node.specifiers) { const { exported } = specifier; - const exportedName = + const exportName = exported.type === "Identifier" ? exported.name : exported.value; - this.checkDuplicateExports(specifier, exportedName); + this.checkDuplicateExports(specifier, exportName); // $FlowIgnore if (!isFrom && specifier.local) { const { local } = specifier; if (local.type !== "Identifier") { - this.raise( - Errors.ExportBindingIsString, - { node: specifier }, - local.value, - exportedName, - ); + this.raise(Errors.ExportBindingIsString, { + at: specifier, + localName: local.value, + exportName, + }); } else { // check for keywords used as local names this.checkReservedWord(local.name, local.loc.start, true, false); @@ -2285,7 +2283,7 @@ export default class StatementParser extends ExpressionParser { // If node.declaration is a class, it will take all decorators in the current context. // Thus we should throw if we see non-empty decorators here. if (currentContextDecorators.length) { - throw this.raise(Errors.UnsupportedDecoratorExport, { node }); + throw this.raise(Errors.UnsupportedDecoratorExport, { at: node }); } } @@ -2318,18 +2316,16 @@ export default class StatementParser extends ExpressionParser { | N.ExportNamedDeclaration | N.ExportSpecifier | N.ExportDefaultSpecifier, - name: string, + exportName: string, ): void { - if (this.exportedIdentifiers.has(name)) { - this.raise( - name === "default" - ? Errors.DuplicateDefaultExport - : Errors.DuplicateExport, - { node }, - name, - ); + if (this.exportedIdentifiers.has(exportName)) { + if (exportName === "default") { + this.raise(Errors.DuplicateDefaultExport, { at: node }); + } else { + this.raise(Errors.DuplicateExport, { at: node, exportName }); + } } - this.exportedIdentifiers.add(name); + this.exportedIdentifiers.add(exportName); } // Parses a comma-separated list of module exports. @@ -2389,11 +2385,10 @@ export default class StatementParser extends ExpressionParser { const result = this.parseStringLiteral(this.state.value); const surrogate = result.value.match(loneSurrogate); if (surrogate) { - this.raise( - Errors.ModuleExportNameHasLoneSurrogate, - { node: result }, - surrogate[0].charCodeAt(0).toString(16), - ); + this.raise(Errors.ModuleExportNameHasLoneSurrogate, { + at: result, + surrogateCharCode: surrogate[0].charCodeAt(0), + }); } return result; } @@ -2457,11 +2452,17 @@ export default class StatementParser extends ExpressionParser { node: N.ImportDeclaration, specifier: N.Node, type: string, - contextDescription: string, ): void { specifier.local = this.parseIdentifier(); - this.checkLVal(specifier.local, contextDescription, BIND_LEXICAL); - node.specifiers.push(this.finishNode(specifier, type)); + node.specifiers.push(this.finishImportSpecifier(specifier, type)); + } + + finishImportSpecifier(specifier: N.Node, type: string) { + this.checkLVal(specifier.local, { + in: specifier, + binding: BIND_LEXICAL, + }); + return this.finishNode(specifier, type); } /** @@ -2488,11 +2489,10 @@ export default class StatementParser extends ExpressionParser { // if a duplicate entry is found, throw an error // for now this logic will come into play only when someone declares `type` twice if (attrNames.has(keyName)) { - this.raise( - Errors.ModuleAttributesWithDuplicateKeys, - { at: this.state.startLoc }, - keyName, - ); + this.raise(Errors.ModuleAttributesWithDuplicateKeys, { + at: this.state.startLoc, + key: keyName, + }); } attrNames.add(keyName); if (this.match(tt.string)) { @@ -2536,19 +2536,16 @@ export default class StatementParser extends ExpressionParser { node.key = this.parseIdentifier(true); if (node.key.name !== "type") { - this.raise( - Errors.ModuleAttributeDifferentFromType, - { node: node.key }, - node.key.name, - ); + this.raise(Errors.ModuleAttributeDifferentFromType, { + at: node.key, + }); } if (attributes.has(node.key.name)) { - this.raise( - Errors.ModuleAttributesWithDuplicateKeys, - { node: node.key }, - node.key.name, - ); + this.raise(Errors.ModuleAttributesWithDuplicateKeys, { + at: node.key, + key: node.key.name, + }); } attributes.add(node.key.name); this.expect(tt.colon); @@ -2589,7 +2586,6 @@ export default class StatementParser extends ExpressionParser { node, this.startNode(), "ImportDefaultSpecifier", - "default import specifier", ); return true; } @@ -2606,7 +2602,6 @@ export default class StatementParser extends ExpressionParser { node, specifier, "ImportNamespaceSpecifier", - "import namespace specifier", ); return true; } @@ -2659,19 +2654,17 @@ export default class StatementParser extends ExpressionParser { } else { const { imported } = specifier; if (importedIsString) { - throw this.raise( - Errors.ImportBindingIsString, - { node: specifier }, - imported.value, - ); + throw this.raise(Errors.ImportBindingIsString, { + at: specifier, + importName: imported.value, + }); } this.checkReservedWord(imported.name, specifier.loc.start, true, true); if (!specifier.local) { specifier.local = cloneIdentifier(imported); } } - this.checkLVal(specifier.local, "import specifier", BIND_LEXICAL); - return this.finishNode(specifier, "ImportSpecifier"); + return this.finishImportSpecifier(specifier, "ImportSpecifier"); } // This is used in flow and typescript plugin diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index f9bad51b28ee..fbe51edefa16 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -3,7 +3,6 @@ import { type Position } from "../util/location"; import { tokenIsLiteralPropertyName, - tokenLabelName, tt, type TokenType, } from "../tokenizer/types"; @@ -19,9 +18,11 @@ import ProductionParameterHandler, { PARAM_AWAIT, PARAM, } from "../util/production-parameter"; -import { Errors, type ErrorTemplate, ErrorCodes } from "./error"; -import type { ParsingError } from "./error"; -import type { PluginConfig } from "./base"; +import { + Errors, + type ParseError, + type ParseErrorConstructor, +} from "../parse-error"; /*:: import type ScopeHandler from "../util/scope"; */ @@ -98,11 +99,13 @@ export default class UtilParser extends Tokenizer { // Asserts that following token is given contextual keyword. - expectContextual(token: TokenType, template?: ErrorTemplate): void { + expectContextual( + token: TokenType, + toParseError?: ParseErrorConstructor, + ): void { if (!this.eatContextual(token)) { - if (template != null) { - /* eslint-disable @babel/development-internal/dry-error-messages */ - throw this.raise(template, { at: this.state.startLoc }); + if (toParseError != null) { + throw this.raise(toParseError, { at: this.state.startLoc }); } throw this.unexpected(null, token); } @@ -150,77 +153,6 @@ export default class UtilParser extends Tokenizer { this.eat(type) || this.unexpected(loc, 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.lastTokEndLoc.index) { - /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise( - { - code: ErrorCodes.SyntaxError, - reasonCode: "UnexpectedSpace", - template: message, - }, - { at: this.state.lastTokEndLoc }, - /* eslint-enable @babel/development-internal/dry-error-messages */ - ); - } - } - - // Raise an unexpected token error. Can take the expected token type - // instead of a message string. - - unexpected(loc?: ?Position, type?: ?TokenType): empty { - /* eslint-disable @babel/development-internal/dry-error-messages */ - throw this.raise( - { - code: ErrorCodes.SyntaxError, - reasonCode: "UnexpectedToken", - template: - type != null - ? `Unexpected token, expected "${tokenLabelName(type)}"` - : "Unexpected token", - }, - { at: loc != null ? loc : this.state.startLoc }, - ); - /* eslint-enable @babel/development-internal/dry-error-messages */ - } - - getPluginNamesFromConfigs(pluginConfigs: Array): Array { - return pluginConfigs.map(c => { - if (typeof c === "string") { - return c; - } else { - return c[0]; - } - }); - } - - expectPlugin(pluginConfig: PluginConfig, loc?: ?Position): true { - if (!this.hasPlugin(pluginConfig)) { - throw this.raiseWithData( - loc != null ? loc : this.state.startLoc, - { missingPlugin: this.getPluginNamesFromConfigs([pluginConfig]) }, - `This experimental syntax requires enabling the parser plugin: ${JSON.stringify( - pluginConfig, - )}.`, - ); - } - - return true; - } - - expectOnePlugin(pluginConfigs: Array): void { - if (!pluginConfigs.some(c => this.hasPlugin(c))) { - throw this.raiseWithData( - this.state.startLoc, - { missingPlugin: this.getPluginNamesFromConfigs(pluginConfigs) }, - `This experimental syntax requires enabling one of the following parser plugin(s): ${pluginConfigs - .map(c => JSON.stringify(c)) - .join(", ")}.`, - ); - } - } - // tryParse will clone parser state. // It is expensive and should be used with cautions tryParse>( @@ -228,7 +160,7 @@ export default class UtilParser extends Tokenizer { oldState: State = this.state.clone(), ): | TryParse - | TryParse + | TryParse, boolean, false, State> | TryParse { const abortSignal: { node: T | null } = { node: null }; try { @@ -245,7 +177,7 @@ export default class UtilParser extends Tokenizer { this.state.tokensLength = failState.tokensLength; return { node, - error: (failState.errors[oldState.errors.length]: ParsingError), + error: (failState.errors[oldState.errors.length]: ParseError), thrown: false, aborted: false, failState, @@ -394,16 +326,16 @@ export default class UtilParser extends Tokenizer { const oldScope = this.scope; const ScopeHandler = this.getScopeHandler(); - this.scope = new ScopeHandler(this.raise.bind(this), this.inModule); + this.scope = new ScopeHandler(this, inModule); const oldProdParam = this.prodParam; this.prodParam = new ProductionParameterHandler(); const oldClassScope = this.classScope; - this.classScope = new ClassScopeHandler(this.raise.bind(this)); + this.classScope = new ClassScopeHandler(this); const oldExpressionScope = this.expressionScope; - this.expressionScope = new ExpressionScopeHandler(this.raise.bind(this)); + this.expressionScope = new ExpressionScopeHandler(this); return () => { // Revert state diff --git a/packages/babel-parser/src/plugins/estree.js b/packages/babel-parser/src/plugins/estree.js index 5c8a1aa3c23f..c09187f99d82 100644 --- a/packages/babel-parser/src/plugins/estree.js +++ b/packages/babel-parser/src/plugins/estree.js @@ -6,7 +6,7 @@ import type { ExpressionErrors } from "../parser/util"; import * as N from "../types"; import type { Node as NodeType, NodeBase, File } from "../types"; import type { Position } from "../util/location"; -import { Errors } from "../parser/error"; +import { Errors } from "../parse-error"; const { defineProperty } = Object; const toUnenumerable = (object, key) => @@ -343,6 +343,10 @@ export default (superClass: Class): Class => return (node: any); } + isValidLVal(type: string, ...rest) { + return type === "Property" ? "value" : super.isValidLVal(type, ...rest); + } + isAssignable(node: N.Node, isBinding?: boolean): boolean { if (node != null && this.isObjectProperty(node)) { return this.isAssignable(node.value, isBinding); @@ -368,9 +372,9 @@ export default (superClass: Class): Class => toAssignableObjectExpressionProp(prop: N.Node, ...args) { if (prop.kind === "get" || prop.kind === "set") { - this.raise(Errors.PatternHasAccessor, { node: prop.key }); + this.raise(Errors.PatternHasAccessor, { at: prop.key }); } else if (prop.method) { - this.raise(Errors.PatternHasMethod, { node: prop.key }); + this.raise(Errors.PatternHasMethod, { at: prop.key }); } else { super.toAssignableObjectExpressionProp(prop, ...args); } diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index da51b8135e93..bd7a0ec63717 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -2,9 +2,6 @@ /*:: declare var invariant; */ -// Error messages are colocated with the plugin. -/* eslint-disable @babel/development-internal/dry-error-messages */ - import type Parser from "../../parser"; import { tokenIsIdentifier, @@ -23,7 +20,6 @@ import * as charCodes from "charcodes"; import { isIteratorStart } from "../../util/identifier"; import FlowScopeHandler from "./scope"; import { - type BindingTypes, BIND_LEXICAL, BIND_VAR, BIND_FUNCTION, @@ -33,7 +29,7 @@ import { SCOPE_OTHER, } from "../../util/scopeflags"; import type { ExpressionErrors } from "../../parser/util"; -import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error"; +import { Errors, ParseErrorEnum } from "../../parse-error"; import { cloneIdentifier } from "../../parser/node"; const reservedTypes = new Set([ @@ -57,97 +53,191 @@ const reservedTypes = new Set([ /* eslint sort-keys: "error" */ // The Errors key follows https://github.com/facebook/flow/blob/master/src/parser/parse_error.ml unless it does not exist -const FlowErrors = makeErrorTemplates( - { - AmbiguousConditionalArrow: - "Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.", - AmbiguousDeclareModuleKind: - "Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module.", - AssignReservedType: "Cannot overwrite reserved type %0.", - DeclareClassElement: - "The `declare` modifier can only appear on class fields.", - DeclareClassFieldInitializer: - "Initializers are not allowed in fields with the `declare` modifier.", - DuplicateDeclareModuleExports: - "Duplicate `declare module.exports` statement.", - EnumBooleanMemberNotInitialized: - "Boolean enum members need to be initialized. Use either `%0 = true,` or `%0 = false,` in enum `%1`.", - EnumDuplicateMemberName: - "Enum member names need to be unique, but the name `%0` has already been used before in enum `%1`.", - EnumInconsistentMemberValues: - "Enum `%0` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers.", - EnumInvalidExplicitType: - "Enum type `%1` is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.", - EnumInvalidExplicitTypeUnknownSupplied: - "Supplied enum type is not valid. Use one of `boolean`, `number`, `string`, or `symbol` in enum `%0`.", - EnumInvalidMemberInitializerPrimaryType: - "Enum `%0` has type `%2`, so the initializer of `%1` needs to be a %2 literal.", - EnumInvalidMemberInitializerSymbolType: - "Symbol enum members cannot be initialized. Use `%1,` in enum `%0`.", - EnumInvalidMemberInitializerUnknownType: - "The enum member initializer for `%1` needs to be a literal (either a boolean, number, or string) in enum `%0`.", - EnumInvalidMemberName: - "Enum member names cannot start with lowercase 'a' through 'z'. Instead of using `%0`, consider using `%1`, in enum `%2`.", - EnumNumberMemberNotInitialized: - "Number enum members need to be initialized, e.g. `%1 = 1` in enum `%0`.", - EnumStringMemberInconsistentlyInitailized: - "String enum members need to consistently either all use initializers, or use no initializers, in enum `%0`.", - GetterMayNotHaveThisParam: "A getter cannot have a `this` parameter.", - ImportTypeShorthandOnlyInPureImport: - "The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements.", - InexactInsideExact: - "Explicit inexact syntax cannot appear inside an explicit exact object type.", - InexactInsideNonObject: - "Explicit inexact syntax cannot appear in class or interface definitions.", - InexactVariance: "Explicit inexact syntax cannot have variance.", - InvalidNonTypeImportInDeclareModule: - "Imports within a `declare module` body must always be `import type` or `import typeof`.", - MissingTypeParamDefault: - "Type parameter declaration needs a default, since a preceding type parameter declaration has a default.", - NestedDeclareModule: - "`declare module` cannot be used inside another `declare module`.", - NestedFlowComment: - "Cannot have a flow comment inside another flow comment.", - PatternIsOptional: - "A binding pattern parameter cannot be optional in an implementation signature.", - SetterMayNotHaveThisParam: "A setter cannot have a `this` parameter.", - SpreadVariance: "Spread properties cannot have variance.", - ThisParamAnnotationRequired: - "A type annotation is required for the `this` parameter.", - ThisParamBannedInConstructor: - "Constructors cannot have a `this` parameter; constructors don't bind `this` like other functions.", - ThisParamMayNotBeOptional: "The `this` parameter cannot be optional.", - ThisParamMustBeFirst: - "The `this` parameter must be the first function parameter.", - ThisParamNoDefault: "The `this` parameter may not have a default value.", - TypeBeforeInitializer: - "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.", - TypeCastInPattern: - "The type cast expression is expected to be wrapped with parenthesis.", - UnexpectedExplicitInexactInObject: - "Explicit inexact syntax must appear at the end of an inexact object.", - UnexpectedReservedType: "Unexpected reserved type %0.", - UnexpectedReservedUnderscore: - "`_` is only allowed as a type argument to call or new.", - UnexpectedSpaceBetweenModuloChecks: - "Spaces between `%` and `checks` are not allowed here.", - UnexpectedSpreadType: - "Spread operator cannot appear in class or interface definitions.", - UnexpectedSubtractionOperand: - 'Unexpected token, expected "number" or "bigint".', - UnexpectedTokenAfterTypeParameter: - "Expected an arrow function after this type parameter declaration.", - UnexpectedTypeParameterBeforeAsyncArrowFunction: - "Type parameters must come after the async keyword, e.g. instead of ` async () => {}`, use `async () => {}`.", - UnsupportedDeclareExportKind: - "`declare export %0` is not supported. Use `%1` instead.", - UnsupportedStatementInDeclareModule: - "Only declares and type imports are allowed inside declare module.", - UnterminatedFlowComment: "Unterminated flow-comment.", - }, - /* code */ ErrorCodes.SyntaxError, - /* syntaxPlugin */ "flow", -); +const FlowErrors = ParseErrorEnum`flow`(_ => ({ + AmbiguousConditionalArrow: _( + "Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.", + ), + AmbiguousDeclareModuleKind: _( + "Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module.", + ), + // TODO: When we get proper string enums in typescript make this ReservedType. + // Not really worth it to do the whole $Values dance with reservedTypes set. + AssignReservedType: _<{| reservedType: string |}>( + ({ reservedType }) => `Cannot overwrite reserved type ${reservedType}.`, + ), + DeclareClassElement: _( + "The `declare` modifier can only appear on class fields.", + ), + DeclareClassFieldInitializer: _( + "Initializers are not allowed in fields with the `declare` modifier.", + ), + DuplicateDeclareModuleExports: _( + "Duplicate `declare module.exports` statement.", + ), + EnumBooleanMemberNotInitialized: _<{| + memberName: string, + enumName: string, + |}>( + ({ memberName, enumName }) => + `Boolean enum members need to be initialized. Use either \`${memberName} = true,\` or \`${memberName} = false,\` in enum \`${enumName}\`.`, + ), + EnumDuplicateMemberName: _<{| memberName: string, enumName: string |}>( + ({ memberName, enumName }) => + `Enum member names need to be unique, but the name \`${memberName}\` has already been used before in enum \`${enumName}\`.`, + ), + EnumInconsistentMemberValues: _<{| enumName: string |}>( + ({ enumName }) => + `Enum \`${enumName}\` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers.`, + ), + EnumInvalidExplicitType: _<{| invalidEnumType: string, enumName: string |}>( + ({ invalidEnumType, enumName }) => + `Enum type \`${invalidEnumType}\` is not valid. Use one of \`boolean\`, \`number\`, \`string\`, or \`symbol\` in enum \`${enumName}\`.`, + ), + EnumInvalidExplicitTypeUnknownSupplied: _<{| enumName: string |}>( + ({ enumName }) => + `Supplied enum type is not valid. Use one of \`boolean\`, \`number\`, \`string\`, or \`symbol\` in enum \`${enumName}\`.`, + ), + + // TODO: When moving to typescript, we should either have each of the + // following errors only accept the specific strings they want: + // + // ...PrimaryType: explicitType: "string" | "number" | "boolean" + // ...SymbolType: explicitType: "symbol" + // ...UnknownType: explicitType: null + // + // Or, alternatively, merge these three errors together into one + // `EnumInvalidMemberInitializer` error that can accept `EnumExplicitType` + // without alteration, and then just have its message change based on the + // explicitType. + EnumInvalidMemberInitializerPrimaryType: _<{| + enumName: string, + memberName: string, + explicitType: EnumExplicitType, + |}>( + ({ enumName, memberName, explicitType }) => + // $FlowIgnore (coercing null which never actually happens) + `Enum \`${enumName}\` has type \`${explicitType}\`, so the initializer of \`${memberName}\` needs to be a ${explicitType} literal.`, + ), + EnumInvalidMemberInitializerSymbolType: _<{| + enumName: string, + memberName: string, + explicitType: EnumExplicitType, + |}>( + ({ enumName, memberName }) => + `Symbol enum members cannot be initialized. Use \`${memberName},\` in enum \`${enumName}\`.`, + ), + EnumInvalidMemberInitializerUnknownType: _<{| + enumName: string, + memberName: string, + explicitType: EnumExplicitType, + |}>( + ({ enumName, memberName }) => + `The enum member initializer for \`${memberName}\` needs to be a literal (either a boolean, number, or string) in enum \`${enumName}\`.`, + ), + EnumInvalidMemberName: _<{| + enumName: string, + memberName: string, + suggestion: string, + |}>( + ({ enumName, memberName, suggestion }) => + `Enum member names cannot start with lowercase 'a' through 'z'. Instead of using \`${memberName}\`, consider using \`${suggestion}\`, in enum \`${enumName}\`.`, + ), + EnumNumberMemberNotInitialized: _<{| + enumName: string, + memberName: string, + |}>( + ({ enumName, memberName }) => + `Number enum members need to be initialized, e.g. \`${memberName} = 1\` in enum \`${enumName}\`.`, + ), + EnumStringMemberInconsistentlyInitailized: _<{| enumName: string |}>( + ({ enumName }) => + `String enum members need to consistently either all use initializers, or use no initializers, in enum \`${enumName}\`.`, + ), + GetterMayNotHaveThisParam: _("A getter cannot have a `this` parameter."), + ImportTypeShorthandOnlyInPureImport: _( + "The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements.", + ), + InexactInsideExact: _( + "Explicit inexact syntax cannot appear inside an explicit exact object type.", + ), + InexactInsideNonObject: _( + "Explicit inexact syntax cannot appear in class or interface definitions.", + ), + InexactVariance: _("Explicit inexact syntax cannot have variance."), + InvalidNonTypeImportInDeclareModule: _( + "Imports within a `declare module` body must always be `import type` or `import typeof`.", + ), + MissingTypeParamDefault: _( + "Type parameter declaration needs a default, since a preceding type parameter declaration has a default.", + ), + NestedDeclareModule: _( + "`declare module` cannot be used inside another `declare module`.", + ), + NestedFlowComment: _( + "Cannot have a flow comment inside another flow comment.", + ), + PatternIsOptional: _( + "A binding pattern parameter cannot be optional in an implementation signature.", + // For consistency in TypeScript and Flow error codes + !process.env.BABEL_8_BREAKING + ? { reasonCode: "OptionalBindingPattern" } + : {}, + ), + SetterMayNotHaveThisParam: _("A setter cannot have a `this` parameter."), + SpreadVariance: _("Spread properties cannot have variance."), + ThisParamAnnotationRequired: _( + "A type annotation is required for the `this` parameter.", + ), + ThisParamBannedInConstructor: _( + "Constructors cannot have a `this` parameter; constructors don't bind `this` like other functions.", + ), + ThisParamMayNotBeOptional: _("The `this` parameter cannot be optional."), + ThisParamMustBeFirst: _( + "The `this` parameter must be the first function parameter.", + ), + ThisParamNoDefault: _("The `this` parameter may not have a default value."), + TypeBeforeInitializer: _( + "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.", + ), + TypeCastInPattern: _( + "The type cast expression is expected to be wrapped with parenthesis.", + ), + UnexpectedExplicitInexactInObject: _( + "Explicit inexact syntax must appear at the end of an inexact object.", + ), + UnexpectedReservedType: _<{| reservedType: string |}>( + ({ reservedType }) => `Unexpected reserved type ${reservedType}.`, + ), + UnexpectedReservedUnderscore: _( + "`_` is only allowed as a type argument to call or new.", + ), + UnexpectedSpaceBetweenModuloChecks: _( + "Spaces between `%` and `checks` are not allowed here.", + ), + UnexpectedSpreadType: _( + "Spread operator cannot appear in class or interface definitions.", + ), + UnexpectedSubtractionOperand: _( + 'Unexpected token, expected "number" or "bigint".', + ), + UnexpectedTokenAfterTypeParameter: _( + "Expected an arrow function after this type parameter declaration.", + ), + UnexpectedTypeParameterBeforeAsyncArrowFunction: _( + "Type parameters must come after the async keyword, e.g. instead of ` async () => {}`, use `async () => {}`.", + ), + UnsupportedDeclareExportKind: _<{| + unsupportedExportKind: string, + suggestion: string, + |}>( + ({ unsupportedExportKind, suggestion }) => + `\`declare export ${unsupportedExportKind}\` is not supported. Use \`${suggestion}\` instead.`, + ), + UnsupportedStatementInDeclareModule: _( + "Only declares and type imports are allowed inside declare module.", + ), + UnterminatedFlowComment: _("Unterminated flow-comment."), +})); /* eslint-disable sort-keys */ function isEsModuleType(bodyElement: N.Node): boolean { @@ -450,19 +540,19 @@ export default (superClass: Class): Class => if (isEsModuleType(bodyElement)) { if (kind === "CommonJS") { this.raise(FlowErrors.AmbiguousDeclareModuleKind, { - node: bodyElement, + at: bodyElement, }); } kind = "ES"; } else if (bodyElement.type === "DeclareModuleExports") { if (hasModuleExport) { this.raise(FlowErrors.DuplicateDeclareModuleExports, { - node: bodyElement, + at: bodyElement, }); } if (kind === "ES") { this.raise(FlowErrors.AmbiguousDeclareModuleKind, { - node: bodyElement, + at: bodyElement, }); } kind = "CommonJS"; @@ -501,14 +591,11 @@ export default (superClass: Class): Class => !insideModule) ) { const label = this.state.value; - const suggestion = exportSuggestions[label]; - - throw this.raise( - FlowErrors.UnsupportedDeclareExportKind, - { at: this.state.startLoc }, - label, - suggestion, - ); + throw this.raise(FlowErrors.UnsupportedDeclareExportKind, { + at: this.state.startLoc, + unsupportedExportKind: label, + suggestion: exportSuggestions[label], + }); } if ( @@ -676,8 +763,10 @@ export default (superClass: Class): Class => declaration ? FlowErrors.AssignReservedType : FlowErrors.UnexpectedReservedType, - { at: startLoc }, - word, + { + at: startLoc, + reservedType: word, + }, ); } @@ -1154,7 +1243,7 @@ export default (superClass: Class): Class => }); } if (variance) { - this.raise(FlowErrors.InexactVariance, { node: variance }); + this.raise(FlowErrors.InexactVariance, { at: variance }); } return null; @@ -1169,7 +1258,7 @@ export default (superClass: Class): Class => this.unexpected(protoStartLoc); } if (variance) { - this.raise(FlowErrors.SpreadVariance, { node: variance }); + this.raise(FlowErrors.SpreadVariance, { at: variance }); } node.argument = this.flowParseType(); @@ -1205,7 +1294,7 @@ export default (superClass: Class): Class => node.value.this ) { this.raise(FlowErrors.ThisParamBannedInConstructor, { - node: node.value.this, + at: node.value.this, }); } } else { @@ -1240,7 +1329,7 @@ export default (superClass: Class): Class => property.kind === "get" ? FlowErrors.GetterMayNotHaveThisParam : FlowErrors.SetterMayNotHaveThisParam, - { node: property.value.this }, + { at: property.value.this }, ); } @@ -1249,12 +1338,12 @@ export default (superClass: Class): Class => property.kind === "get" ? Errors.BadGetterArity : Errors.BadSetterArity, - { node: property }, + { at: property }, ); } if (property.kind === "set" && property.value.rest) { - this.raise(Errors.BadSetterRestParameter, { node: property }); + this.raise(Errors.BadSetterRestParameter, { at: property }); } } @@ -1336,13 +1425,13 @@ export default (superClass: Class): Class => if (lh.type === tt.colon || lh.type === tt.question) { if (isThis && !first) { - this.raise(FlowErrors.ThisParamMustBeFirst, { node }); + this.raise(FlowErrors.ThisParamMustBeFirst, { at: node }); } name = this.parseIdentifier(isThis); if (this.eat(tt.question)) { optional = true; if (isThis) { - this.raise(FlowErrors.ThisParamMayNotBeOptional, { node }); + this.raise(FlowErrors.ThisParamMayNotBeOptional, { at: node }); } } typeAnnotation = this.flowParseTypeInitialiser(); @@ -2214,7 +2303,7 @@ export default (superClass: Class): Class => this.raise(FlowErrors.DeclareClassElement, { at: startLoc }); } else if (member.value) { this.raise(FlowErrors.DeclareClassFieldInitializer, { - node: member.value, + at: member.value, }); } } @@ -2230,11 +2319,10 @@ export default (superClass: Class): Class => // Allow @@iterator and @@asyncIterator as a identifier only inside type if (!this.isIterator(word) || !this.state.inType) { - this.raise( - Errors.InvalidIdentifier, - { at: this.state.curPosition() }, - fullWord, - ); + this.raise(Errors.InvalidIdentifier, { + at: this.state.curPosition(), + identifierName: fullWord, + }); } this.finishToken(tt.name, fullWord); @@ -2312,7 +2400,7 @@ export default (superClass: Class): Class => (exprList.length > 1 || !isParenthesizedExpr) ) { this.raise(FlowErrors.TypeCastInPattern, { - node: expr.typeAnnotation, + at: expr.typeAnnotation, }); } } @@ -2345,21 +2433,8 @@ export default (superClass: Class): Class => return node; } - checkLVal( - expr: N.Expression, - ...args: - | [string, BindingTypes | void] - | [ - string, - BindingTypes | void, - ?Set, - boolean | void, - boolean | void, - ] - ): void { - if (expr.type !== "TypeCastExpression") { - return super.checkLVal(expr, ...args); - } + isValidLVal(type: string, ...rest) { + return type === "TypeCastExpression" || super.isValidLVal(type, ...rest); } // parse class property type annotations @@ -2422,7 +2497,7 @@ export default (superClass: Class): Class => if (method.params && isConstructor) { const params = method.params; if (params.length > 0 && this.isThisParam(params[0])) { - this.raise(FlowErrors.ThisParamBannedInConstructor, { node: method }); + this.raise(FlowErrors.ThisParamBannedInConstructor, { at: method }); } // estree support } else if ( @@ -2433,7 +2508,7 @@ export default (superClass: Class): Class => ) { const params = method.value.params; if (params.length > 0 && this.isThisParam(params[0])) { - this.raise(FlowErrors.ThisParamBannedInConstructor, { node: method }); + this.raise(FlowErrors.ThisParamBannedInConstructor, { at: method }); } } } @@ -2483,9 +2558,9 @@ export default (superClass: Class): Class => if (params.length > 0) { const param = params[0]; if (this.isThisParam(param) && method.kind === "get") { - this.raise(FlowErrors.GetterMayNotHaveThisParam, { node: param }); + this.raise(FlowErrors.GetterMayNotHaveThisParam, { at: param }); } else if (this.isThisParam(param)) { - this.raise(FlowErrors.SetterMayNotHaveThisParam, { node: param }); + this.raise(FlowErrors.SetterMayNotHaveThisParam, { at: param }); } } } @@ -2540,10 +2615,10 @@ export default (superClass: Class): Class => parseAssignableListItemTypes(param: N.Pattern): N.Pattern { if (this.eat(tt.question)) { if (param.type !== "Identifier") { - this.raise(FlowErrors.PatternIsOptional, { node: param }); + this.raise(FlowErrors.PatternIsOptional, { at: param }); } if (this.isThisParam(param)) { - this.raise(FlowErrors.ThisParamMayNotBeOptional, { node: param }); + this.raise(FlowErrors.ThisParamMayNotBeOptional, { at: param }); } ((param: any): N.Identifier).optional = true; @@ -2551,11 +2626,11 @@ export default (superClass: Class): Class => if (this.match(tt.colon)) { param.typeAnnotation = this.flowParseTypeAnnotation(); } else if (this.isThisParam(param)) { - this.raise(FlowErrors.ThisParamAnnotationRequired, { node: param }); + this.raise(FlowErrors.ThisParamAnnotationRequired, { at: param }); } if (this.match(tt.eq) && this.isThisParam(param)) { - this.raise(FlowErrors.ThisParamNoDefault, { node: param }); + this.raise(FlowErrors.ThisParamNoDefault, { at: param }); } this.resetEndLocation(param); @@ -2575,7 +2650,7 @@ export default (superClass: Class): Class => node.right.start < node.typeAnnotation.start ) { this.raise(FlowErrors.TypeBeforeInitializer, { - node: node.typeAnnotation, + at: node.typeAnnotation, }); } @@ -2594,7 +2669,6 @@ export default (superClass: Class): Class => node: N.ImportDeclaration, specifier: N.Node, type: string, - contextDescription: string, ): void { specifier.local = hasTypeImportKind(node) ? this.flowParseRestrictedIdentifier( @@ -2603,8 +2677,7 @@ export default (superClass: Class): Class => ) : this.parseIdentifier(); - this.checkLVal(specifier.local, contextDescription, BIND_LEXICAL); - node.specifiers.push(this.finishNode(specifier, type)); + node.specifiers.push(this.finishImportSpecifier(specifier, type)); } // parse typeof and type imports @@ -2687,11 +2760,10 @@ export default (superClass: Class): Class => } else { if (importedIsString) { /*:: invariant(firstIdent instanceof N.StringLiteral) */ - throw this.raise( - Errors.ImportBindingIsString, - { node: specifier }, - firstIdent.value, - ); + throw this.raise(Errors.ImportBindingIsString, { + at: specifier, + importName: firstIdent.value, + }); } /*:: invariant(firstIdent instanceof N.Node) */ specifier.imported = firstIdent; @@ -2710,7 +2782,7 @@ export default (superClass: Class): Class => if (isInTypeOnlyImport && specifierIsTypeImport) { this.raise(FlowErrors.ImportTypeShorthandOnlyInPureImport, { - node: specifier, + at: specifier, }); } @@ -2731,8 +2803,7 @@ export default (superClass: Class): Class => ); } - this.checkLVal(specifier.local, "import specifier", BIND_LEXICAL); - return this.finishNode(specifier, "ImportSpecifier"); + return this.finishImportSpecifier(specifier, "ImportSpecifier"); } parseBindingAtom(): N.Pattern { @@ -2885,7 +2956,7 @@ export default (superClass: Class): Class => /*:: invariant(typeParameters) */ this.raise( FlowErrors.UnexpectedTypeParameterBeforeAsyncArrowFunction, - { node: typeParameters }, + { at: typeParameters }, ); } @@ -2918,7 +2989,7 @@ export default (superClass: Class): Class => /*:: invariant(typeParameters) */ throw this.raise(FlowErrors.UnexpectedTokenAfterTypeParameter, { - node: typeParameters, + at: typeParameters, }); } @@ -2993,7 +3064,7 @@ export default (superClass: Class): Class => // ensure the `this` param is first, if it exists for (let i = 0; i < node.params.length; i++) { if (this.isThisParam(node.params[i]) && i > 0) { - this.raise(FlowErrors.ThisParamMustBeFirst, { node: node.params[i] }); + this.raise(FlowErrors.ThisParamMustBeFirst, { at: node.params[i] }); } } @@ -3257,47 +3328,27 @@ export default (superClass: Class): Class => loc: Position, { enumName, memberName }: { enumName: string, memberName: string }, ): void { - this.raise( - FlowErrors.EnumBooleanMemberNotInitialized, - { at: loc }, + this.raise(FlowErrors.EnumBooleanMemberNotInitialized, { + at: loc, memberName, enumName, - ); - } - - flowEnumErrorInvalidExplicitType( - loc: Position, - { - enumName, - suppliedType, - }: { enumName: string, suppliedType: null | string }, - ) { - return this.raise( - suppliedType === null - ? FlowErrors.EnumInvalidExplicitTypeUnknownSupplied - : FlowErrors.EnumInvalidExplicitType, - { at: loc }, - enumName, - suppliedType, - ); + }); } flowEnumErrorInvalidMemberInitializer( loc: Position, - { enumName, explicitType, memberName }: EnumContext, + enumContext: EnumContext, ) { return this.raise( - explicitType === "boolean" || - explicitType === "number" || - explicitType === "string" - ? FlowErrors.EnumInvalidMemberInitializerPrimaryType - : explicitType === "symbol" + !enumContext.explicitType + ? FlowErrors.EnumInvalidMemberInitializerUnknownType + : enumContext.explicitType === "symbol" ? FlowErrors.EnumInvalidMemberInitializerSymbolType - : FlowErrors.EnumInvalidMemberInitializerUnknownType, - { at: loc }, - enumName, - memberName, - explicitType, + : FlowErrors.EnumInvalidMemberInitializerPrimaryType, + { + at: loc, + ...enumContext, + }, ); } @@ -3305,23 +3356,21 @@ export default (superClass: Class): Class => loc: Position, { enumName, memberName }: { enumName: string, memberName: string }, ): void { - this.raise( - FlowErrors.EnumNumberMemberNotInitialized, - { at: loc }, + this.raise(FlowErrors.EnumNumberMemberNotInitialized, { + at: loc, enumName, memberName, - ); + }); } flowEnumErrorStringMemberInconsistentlyInitailized( node: N.Node, { enumName }: { enumName: string }, ): void { - this.raise( - FlowErrors.EnumStringMemberInconsistentlyInitailized, - { node }, + this.raise(FlowErrors.EnumStringMemberInconsistentlyInitailized, { + at: node, enumName, - ); + }); } flowEnumMemberInit(): EnumMemberInit { @@ -3417,22 +3466,19 @@ export default (superClass: Class): Class => continue; } if (/^[a-z]/.test(memberName)) { - this.raise( - FlowErrors.EnumInvalidMemberName, - { node: id }, + this.raise(FlowErrors.EnumInvalidMemberName, { + at: id, memberName, - // suggestion - memberName[0].toUpperCase() + memberName.slice(1), + suggestion: memberName[0].toUpperCase() + memberName.slice(1), enumName, - ); + }); } if (seenNames.has(memberName)) { - this.raise( - FlowErrors.EnumDuplicateMemberName, - { node: id }, + this.raise(FlowErrors.EnumDuplicateMemberName, { + at: id, memberName, enumName, - ); + }); } seenNames.add(memberName); const context = { enumName, explicitType, memberName }; @@ -3526,32 +3572,32 @@ export default (superClass: Class): Class => }: { enumName: string, }): EnumExplicitType { - if (this.eatContextual(tt._of)) { - if (!tokenIsIdentifier(this.state.type)) { - throw this.flowEnumErrorInvalidExplicitType(this.state.startLoc, { - enumName, - suppliedType: null, - }); - } + if (!this.eatContextual(tt._of)) return null; - const { value } = this.state; - this.next(); + if (!tokenIsIdentifier(this.state.type)) { + throw this.raise(FlowErrors.EnumInvalidExplicitTypeUnknownSupplied, { + at: this.state.startLoc, + enumName, + }); + } - if ( - value !== "boolean" && - value !== "number" && - value !== "string" && - value !== "symbol" - ) { - this.flowEnumErrorInvalidExplicitType(this.state.startLoc, { - enumName, - suppliedType: value, - }); - } + const { value } = this.state; + this.next(); - return value; + if ( + value !== "boolean" && + value !== "number" && + value !== "string" && + value !== "symbol" + ) { + this.raise(FlowErrors.EnumInvalidExplicitType, { + at: this.state.startLoc, + enumName, + invalidEnumType: value, + }); } - return null; + + return value; } flowEnumBody(node: N.Node, id: N.Node): N.Node { @@ -3634,11 +3680,10 @@ export default (superClass: Class): Class => this.expect(tt.braceR); return this.finishNode(node, "EnumNumberBody"); } else { - this.raise( - FlowErrors.EnumInconsistentMemberValues, - { at: nameLoc }, + this.raise(FlowErrors.EnumInconsistentMemberValues, { + at: nameLoc, enumName, - ); + }); return empty(); } } diff --git a/packages/babel-parser/src/plugins/jsx/index.js b/packages/babel-parser/src/plugins/jsx/index.js index f65b28476cd0..41e0a761b378 100644 --- a/packages/babel-parser/src/plugins/jsx/index.js +++ b/packages/babel-parser/src/plugins/jsx/index.js @@ -1,8 +1,5 @@ // @flow -// Error messages are colocated with the plugin. -/* eslint-disable @babel/development-internal/dry-error-messages */ - import * as charCodes from "charcodes"; import XHTMLEntities from "./xhtml"; @@ -20,27 +17,37 @@ import * as N from "../../types"; import { isIdentifierChar, isIdentifierStart } from "../../util/identifier"; import type { Position } from "../../util/location"; import { isNewLine } from "../../util/whitespace"; -import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error"; +import { Errors, ParseErrorEnum } from "../../parse-error"; /* eslint sort-keys: "error" */ -const JsxErrors = makeErrorTemplates( - { - AttributeIsEmpty: - "JSX attributes must only be assigned a non-empty expression.", - MissingClosingTagElement: - "Expected corresponding JSX closing tag for <%0>.", - MissingClosingTagFragment: "Expected corresponding JSX closing tag for <>.", - UnexpectedSequenceExpression: - "Sequence expressions cannot be directly nested inside JSX. Did you mean to wrap it in parentheses (...)?", - UnsupportedJsxValue: - "JSX value should be either an expression or a quoted JSX text.", - UnterminatedJsxContent: "Unterminated JSX contents.", - UnwrappedAdjacentJSXElements: - "Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...?", - }, - /* code */ ErrorCodes.SyntaxError, - /* syntaxPlugin */ "jsx", -); +const JsxErrors = ParseErrorEnum`jsx`(_ => ({ + AttributeIsEmpty: _( + "JSX attributes must only be assigned a non-empty expression.", + ), + MissingClosingTagElement: _<{| openingTagName: string |}>( + ({ openingTagName }) => + `Expected corresponding JSX closing tag for <${openingTagName}>.`, + ), + MissingClosingTagFragment: _( + "Expected corresponding JSX closing tag for <>.", + ), + UnexpectedSequenceExpression: _( + "Sequence expressions cannot be directly nested inside JSX. Did you mean to wrap it in parentheses (...)?", + ), + // FIXME: Unify with Errors.UnexpectedToken + UnexpectedToken: _<{| unexpected: string, HTMLEntity: string |}>( + ({ unexpected, HTMLEntity }) => + `Unexpected token \`${unexpected}\`. Did you mean \`${HTMLEntity}\` or \`{'${unexpected}'}\`?`, + ), + UnsupportedJsxValue: _( + "JSX value should be either an expression or a quoted JSX text.", + ), + UnterminatedJsxContent: _("Unterminated JSX contents."), + UnwrappedAdjacentJSXElements: _( + "Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...?", + ), +})); + /* eslint-disable sort-keys */ function isFragment(object: ?N.JSXElement): boolean { @@ -113,17 +120,12 @@ export default (superClass: Class): Class => case charCodes.greaterThan: case charCodes.rightCurlyBrace: if (process.env.BABEL_8_BREAKING) { - const htmlEntity = - ch === charCodes.rightCurlyBrace ? "}" : ">"; - const char = this.input[this.state.pos]; - this.raise( - { - code: ErrorCodes.SyntaxError, - reasonCode: "UnexpectedToken", - template: `Unexpected token \`${char}\`. Did you mean \`${htmlEntity}\` or \`{'${char}'}\`?`, - }, - { at: this.state.curPosition() }, - ); + this.raise(JsxErrors.UnexpectedToken, { + at: this.state.curPosition(), + unexpected: this.input[this.state.pos], + HTMLEntity: + ch === charCodes.rightCurlyBrace ? "}" : ">", + }); } /* falls through */ @@ -318,7 +320,7 @@ export default (superClass: Class): Class => this.next(); node = this.jsxParseExpressionContainer(node, tc.j_oTag); if (node.expression.type === "JSXEmptyExpression") { - this.raise(JsxErrors.AttributeIsEmpty, { node }); + this.raise(JsxErrors.AttributeIsEmpty, { at: node }); } return node; @@ -373,7 +375,7 @@ export default (superClass: Class): Class => !expression.extra?.parenthesized ) { this.raise(JsxErrors.UnexpectedSequenceExpression, { - node: expression.expressions[1], + at: expression.expressions[1], }); } } @@ -504,27 +506,25 @@ export default (superClass: Class): Class => closingElement !== null ) { this.raise(JsxErrors.MissingClosingTagFragment, { - node: closingElement, + at: closingElement, }); } else if (!isFragment(openingElement) && isFragment(closingElement)) { - this.raise( - JsxErrors.MissingClosingTagElement, + this.raise(JsxErrors.MissingClosingTagElement, { // $FlowIgnore - { node: closingElement }, - getQualifiedJSXName(openingElement.name), - ); + at: closingElement, + openingTagName: getQualifiedJSXName(openingElement.name), + }); } else if (!isFragment(openingElement) && !isFragment(closingElement)) { if ( // $FlowIgnore getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name) ) { - this.raise( - JsxErrors.MissingClosingTagElement, + this.raise(JsxErrors.MissingClosingTagElement, { // $FlowIgnore - { node: closingElement }, - getQualifiedJSXName(openingElement.name), - ); + at: closingElement, + openingTagName: getQualifiedJSXName(openingElement.name), + }); } } } diff --git a/packages/babel-parser/src/plugins/placeholders.js b/packages/babel-parser/src/plugins/placeholders.js index c89f395f3345..3b28fc968b65 100644 --- a/packages/babel-parser/src/plugins/placeholders.js +++ b/packages/babel-parser/src/plugins/placeholders.js @@ -1,14 +1,11 @@ // @flow -// Error messages are colocated with the plugin. -/* eslint-disable @babel/development-internal/dry-error-messages */ - import * as charCodes from "charcodes"; import { tokenLabelName, tt } from "../tokenizer/types"; import type Parser from "../parser"; import * as N from "../types"; -import { makeErrorTemplates, ErrorCodes } from "../parser/error"; +import { ParseErrorEnum } from "../parse-error"; export type PlaceholderTypes = | "Identifier" @@ -50,13 +47,10 @@ type NodeOf = $Switch< type MaybePlaceholder = NodeOf; // | Placeholder /* eslint sort-keys: "error" */ -const PlaceholderErrors = makeErrorTemplates( - { - ClassNameIsRequired: "A class name is required.", - }, - /* code */ ErrorCodes.SyntaxError, - /* syntaxPlugin */ "placeholders", -); +const PlaceholderErrors = ParseErrorEnum`placeholders`(_ => ({ + ClassNameIsRequired: _("A class name is required."), + UnexpectedSpace: _("Unexpected space in placeholder."), +})); /* eslint-disable sort-keys */ export default (superClass: Class): Class => @@ -67,13 +61,13 @@ export default (superClass: Class): Class => if (this.match(tt.placeholder)) { const node = this.startNode(); this.next(); - this.assertNoSpace("Unexpected space in placeholder."); + this.assertNoSpace(); // 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.assertNoSpace(); this.expect(tt.placeholder); return this.finishPlaceholder(node, expectedNode); } @@ -142,8 +136,8 @@ export default (superClass: Class): Class => ); } - checkLVal(expr: N.Expression): void { - if (expr.type !== "Placeholder") super.checkLVal(...arguments); + isValidLVal(type: string, ...rest) { + return type === "Placeholder" || super.isValidLVal(type, ...rest); } toAssignable(node: N.Node): N.Node { @@ -367,4 +361,13 @@ export default (superClass: Class): Class => super.parseImportSource(...arguments) ); } + + // Throws if the current token and the prev one are separated by a space. + assertNoSpace(): void { + if (this.state.start > this.state.lastTokEndLoc.index) { + this.raise(PlaceholderErrors.UnexpectedSpace, { + at: this.state.lastTokEndLoc, + }); + } + } }; diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index fae6d2fbeb36..0cd8a0a15759 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -2,9 +2,6 @@ /*:: declare var invariant; */ -// Error messages are colocated with the plugin. -/* eslint-disable @babel/development-internal/dry-error-messages */ - import type State from "../../tokenizer/state"; import { tokenIsIdentifier, @@ -32,19 +29,18 @@ import { BIND_TS_NAMESPACE, BIND_CLASS, BIND_LEXICAL, + BIND_NONE, } from "../../util/scopeflags"; import TypeScriptScopeHandler from "./scope"; import * as charCodes from "charcodes"; import type { ExpressionErrors } from "../../parser/util"; import { PARAM } from "../../util/production-parameter"; -import { - Errors, - makeErrorTemplates, - type ErrorTemplate, - ErrorCodes, -} from "../../parser/error"; +import { Errors, ParseErrorEnum } from "../../parse-error"; import { cloneIdentifier } from "../../parser/node"; +const getOwn = (object, key) => + Object.hasOwnProperty.call(object, key) && object[key]; + type TsModifier = | "readonly" | "abstract" @@ -75,108 +71,182 @@ type ParsingContext = | "TypeParametersOrArguments"; /* eslint sort-keys: "error" */ -const TSErrors = makeErrorTemplates( - { - AbstractMethodHasImplementation: - "Method '%0' cannot have an implementation because it is marked abstract.", - AbstractPropertyHasInitializer: - "Property '%0' cannot have an initializer because it is marked abstract.", - AccesorCannotDeclareThisParameter: - "'get' and 'set' accessors cannot declare 'this' parameters.", - AccesorCannotHaveTypeParameters: "An accessor cannot have type parameters.", - ClassMethodHasDeclare: "Class methods cannot have the 'declare' modifier.", - ClassMethodHasReadonly: - "Class methods cannot have the 'readonly' modifier.", - ConstructorHasTypeParameters: - "Type parameters cannot appear on a constructor declaration.", - DeclareAccessor: "'declare' is not allowed in %0ters.", - DeclareClassFieldHasInitializer: - "Initializers are not allowed in ambient contexts.", - DeclareFunctionHasImplementation: - "An implementation cannot be declared in ambient contexts.", - DuplicateAccessibilityModifier: "Accessibility modifier already seen.", - DuplicateModifier: "Duplicate modifier: '%0'.", - EmptyHeritageClauseType: "'%0' list cannot be empty.", - EmptyTypeArguments: "Type argument list cannot be empty.", - EmptyTypeParameters: "Type parameter list cannot be empty.", - ExpectedAmbientAfterExportDeclare: - "'export declare' must be followed by an ambient declaration.", - ImportAliasHasImportType: "An import alias can not use 'import type'.", - IncompatibleModifiers: "'%0' modifier cannot be used with '%1' modifier.", - IndexSignatureHasAbstract: - "Index signatures cannot have the 'abstract' modifier.", - IndexSignatureHasAccessibility: - "Index signatures cannot have an accessibility modifier ('%0').", - IndexSignatureHasDeclare: - "Index signatures cannot have the 'declare' modifier.", - IndexSignatureHasOverride: - "'override' modifier cannot appear on an index signature.", - IndexSignatureHasStatic: - "Index signatures cannot have the 'static' modifier.", - InvalidModifierOnTypeMember: - "'%0' modifier cannot appear on a type member.", - InvalidModifiersOrder: "'%0' modifier must precede '%1' modifier.", - InvalidTupleMemberLabel: - "Tuple members must be labeled with a simple identifier.", - MissingInterfaceName: - "'interface' declarations must be followed by an identifier.", - MixedLabeledAndUnlabeledElements: - "Tuple members must all have names or all not have names.", - NonAbstractClassHasAbstractMethod: - "Abstract methods can only appear within an abstract class.", - NonClassMethodPropertyHasAbstractModifer: - "'abstract' modifier can only appear on a class, method, or property declaration.", - OptionalTypeBeforeRequired: - "A required element cannot follow an optional element.", - OverrideNotInSubClass: - "This member cannot have an 'override' modifier because its containing class does not extend another class.", - PatternIsOptional: - "A binding pattern parameter cannot be optional in an implementation signature.", - PrivateElementHasAbstract: - "Private elements cannot have the 'abstract' modifier.", - PrivateElementHasAccessibility: - "Private elements cannot have an accessibility modifier ('%0').", - ReadonlyForMethodSignature: - "'readonly' modifier can only appear on a property declaration or index signature.", - ReservedArrowTypeParam: - "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `() => ...`.", - ReservedTypeAssertion: - "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.", - SetAccesorCannotHaveOptionalParameter: - "A 'set' accessor cannot have an optional parameter.", - SetAccesorCannotHaveRestParameter: - "A 'set' accessor cannot have rest parameter.", - SetAccesorCannotHaveReturnType: - "A 'set' accessor cannot have a return type annotation.", - SingleTypeParameterWithoutTrailingComma: - "Single type parameter %0 should have a trailing comma. Example usage: <%0,>.", - StaticBlockCannotHaveModifier: - "Static class blocks cannot have any modifier.", - TypeAnnotationAfterAssign: - "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.", - TypeImportCannotSpecifyDefaultAndNamed: - "A type-only import can specify a default import or named bindings, but not both.", - TypeModifierIsUsedInTypeExports: - "The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.", - TypeModifierIsUsedInTypeImports: - "The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.", - UnexpectedParameterModifier: - "A parameter property is only allowed in a constructor implementation.", - UnexpectedReadonly: - "'readonly' type modifier is only permitted on array and tuple literal types.", - UnexpectedTypeAnnotation: "Did not expect a type annotation here.", - UnexpectedTypeCastInParameter: - "Unexpected type cast in parameter position.", - UnsupportedImportTypeArgument: - "Argument in a type import must be a string literal.", - UnsupportedParameterPropertyKind: - "A parameter property may not be declared using a binding pattern.", - UnsupportedSignatureParameterKind: - "Name in a signature must be an Identifier, ObjectPattern or ArrayPattern, instead got %0.", - }, - /* code */ ErrorCodes.SyntaxError, - /* syntaxPlugin */ "typescript", -); +const TSErrors = ParseErrorEnum`typescript`(_ => ({ + AbstractMethodHasImplementation: _<{| methodName: string |}>( + ({ methodName }) => + `Method '${methodName}' cannot have an implementation because it is marked abstract.`, + ), + AbstractPropertyHasInitializer: _<{| propertyName: string |}>( + ({ propertyName }) => + `Property '${propertyName}' cannot have an initializer because it is marked abstract.`, + ), + AccesorCannotDeclareThisParameter: _( + "'get' and 'set' accessors cannot declare 'this' parameters.", + ), + AccesorCannotHaveTypeParameters: _( + "An accessor cannot have type parameters.", + ), + CannotFindName: _<{| name: string |}>( + ({ name }) => `Cannot find name '${name}'.`, + ), + ClassMethodHasDeclare: _("Class methods cannot have the 'declare' modifier."), + ClassMethodHasReadonly: _( + "Class methods cannot have the 'readonly' modifier.", + ), + ConstInitiailizerMustBeStringOrNumericLiteralOrLiteralEnumReference: _( + "A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference.", + ), + ConstructorHasTypeParameters: _( + "Type parameters cannot appear on a constructor declaration.", + ), + DeclareAccessor: _<{| kind: "get" | "set" |}>( + ({ kind }) => `'declare' is not allowed in ${kind}ters.`, + ), + DeclareClassFieldHasInitializer: _( + "Initializers are not allowed in ambient contexts.", + ), + DeclareFunctionHasImplementation: _( + "An implementation cannot be declared in ambient contexts.", + ), + DuplicateAccessibilityModifier: _<{| modifier: N.Accessibility |}>( + // `Accessibility modifier already seen: ${modifier}` would be more helpful. + // eslint-disable-next-line no-unused-vars + ({ modifier }) => `Accessibility modifier already seen.`, + ), + DuplicateModifier: _<{| modifier: TsModifier |}>( + ({ modifier }) => `Duplicate modifier: '${modifier}'.`, + ), + // `token` matches the terminology used by typescript: + // https://github.com/microsoft/TypeScript/blob/main/src/compiler/types.ts#L2915 + EmptyHeritageClauseType: _<{| token: "extends" | "implements" |}>( + ({ token }) => `'${token}' list cannot be empty.`, + ), + EmptyTypeArguments: _("Type argument list cannot be empty."), + EmptyTypeParameters: _("Type parameter list cannot be empty."), + ExpectedAmbientAfterExportDeclare: _( + "'export declare' must be followed by an ambient declaration.", + ), + ImportAliasHasImportType: _("An import alias can not use 'import type'."), + IncompatibleModifiers: _<{| modifiers: [TsModifier, TsModifier] |}>( + ({ modifiers }) => + `'${modifiers[0]}' modifier cannot be used with '${modifiers[1]}' modifier.`, + ), + IndexSignatureHasAbstract: _( + "Index signatures cannot have the 'abstract' modifier.", + ), + IndexSignatureHasAccessibility: _<{| modifier: N.Accessibility |}>( + ({ modifier }) => + `Index signatures cannot have an accessibility modifier ('${modifier}').`, + ), + IndexSignatureHasDeclare: _( + "Index signatures cannot have the 'declare' modifier.", + ), + IndexSignatureHasOverride: _( + "'override' modifier cannot appear on an index signature.", + ), + IndexSignatureHasStatic: _( + "Index signatures cannot have the 'static' modifier.", + ), + InitializerNotAllowedInAmbientContext: _( + "Initializers are not allowed in ambient contexts.", + ), + InvalidModifierOnTypeMember: _<{| modifier: TsModifier |}>( + ({ modifier }) => `'${modifier}' modifier cannot appear on a type member.`, + ), + InvalidModifiersOrder: _<{| orderedModifiers: [TsModifier, TsModifier] |}>( + ({ orderedModifiers }) => + `'${orderedModifiers[0]}' modifier must precede '${orderedModifiers[1]}' modifier.`, + ), + InvalidTupleMemberLabel: _( + "Tuple members must be labeled with a simple identifier.", + ), + MissingInterfaceName: _( + "'interface' declarations must be followed by an identifier.", + ), + MixedLabeledAndUnlabeledElements: _( + "Tuple members must all have names or all not have names.", + ), + NonAbstractClassHasAbstractMethod: _( + "Abstract methods can only appear within an abstract class.", + ), + NonClassMethodPropertyHasAbstractModifer: _( + "'abstract' modifier can only appear on a class, method, or property declaration.", + ), + OptionalTypeBeforeRequired: _( + "A required element cannot follow an optional element.", + ), + OverrideNotInSubClass: _( + "This member cannot have an 'override' modifier because its containing class does not extend another class.", + ), + PatternIsOptional: _( + "A binding pattern parameter cannot be optional in an implementation signature.", + ), + PrivateElementHasAbstract: _( + "Private elements cannot have the 'abstract' modifier.", + ), + PrivateElementHasAccessibility: _<{| modifier: N.Accessibility |}>( + ({ modifier }) => + `Private elements cannot have an accessibility modifier ('${modifier}').`, + ), + ReadonlyForMethodSignature: _( + "'readonly' modifier can only appear on a property declaration or index signature.", + ), + ReservedArrowTypeParam: _( + "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `() => ...`.", + ), + ReservedTypeAssertion: _( + "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.", + ), + SetAccesorCannotHaveOptionalParameter: _( + "A 'set' accessor cannot have an optional parameter.", + ), + SetAccesorCannotHaveRestParameter: _( + "A 'set' accessor cannot have rest parameter.", + ), + SetAccesorCannotHaveReturnType: _( + "A 'set' accessor cannot have a return type annotation.", + ), + SingleTypeParameterWithoutTrailingComma: _<{| typeParameterName: string |}>( + ({ typeParameterName }) => + `Single type parameter ${typeParameterName} should have a trailing comma. Example usage: <${typeParameterName},>.`, + ), + StaticBlockCannotHaveModifier: _( + "Static class blocks cannot have any modifier.", + ), + TypeAnnotationAfterAssign: _( + "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.", + ), + TypeImportCannotSpecifyDefaultAndNamed: _( + "A type-only import can specify a default import or named bindings, but not both.", + ), + TypeModifierIsUsedInTypeExports: _( + "The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.", + ), + TypeModifierIsUsedInTypeImports: _( + "The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.", + ), + UnexpectedParameterModifier: _( + "A parameter property is only allowed in a constructor implementation.", + ), + UnexpectedReadonly: _( + "'readonly' type modifier is only permitted on array and tuple literal types.", + ), + UnexpectedTypeAnnotation: _("Did not expect a type annotation here."), + UnexpectedTypeCastInParameter: _( + "Unexpected type cast in parameter position.", + ), + UnsupportedImportTypeArgument: _( + "Argument in a type import must be a string literal.", + ), + UnsupportedParameterPropertyKind: _( + "A parameter property may not be declared using a binding pattern.", + ), + UnsupportedSignatureParameterKind: _<{| type: string |}>( + ({ type }) => + `Name in a signature must be an Identifier, ObjectPattern or ArrayPattern, instead got ${type}.`, + ), +})); + /* eslint-disable sort-keys */ // Doesn't handle "void" or "null" because those are keywords, not identifiers. @@ -272,27 +342,29 @@ export default (superClass: Class): Class => /** Parses a list of modifiers, in any order. * If you need a specific order, you must call this function multiple times: - * this.tsParseModifiers(node, ["public"]); - * this.tsParseModifiers(node, ["abstract", "readonly"]); + * this.tsParseModifiers({ modified: node, allowedModifiers: ["public"] }); + * this.tsParseModifiers({ modified: node, allowedModifiers: ["abstract", "readonly"] }); */ - tsParseModifiers( + tsParseModifiers({ + modified, + allowedModifiers, + disallowedModifiers, + stopOnStartOfClassStaticBlock, + }: { modified: { [key: TsModifier]: ?true, accessibility?: N.Accessibility, }, allowedModifiers: TsModifier[], disallowedModifiers?: TsModifier[], - errorTemplate?: ErrorTemplate, stopOnStartOfClassStaticBlock?: boolean, - ): void { + }): void { const enforceOrder = (loc, modifier, before, after) => { if (modifier === before && modified[after]) { - this.raise( - TSErrors.InvalidModifiersOrder, - { at: loc }, - before, - after, - ); + this.raise(TSErrors.InvalidModifiersOrder, { + at: loc, + orderedModifiers: [before, after], + }); } }; const incompatible = (loc, modifier, mod1, mod2) => { @@ -300,7 +372,10 @@ export default (superClass: Class): Class => (modified[mod1] && modifier === mod2) || (modified[mod2] && modifier === mod1) ) { - this.raise(TSErrors.IncompatibleModifiers, { at: loc }, mod1, mod2); + this.raise(TSErrors.IncompatibleModifiers, { + at: loc, + modifiers: [mod1, mod2], + }); } }; @@ -317,6 +392,7 @@ export default (superClass: Class): Class => if (modified.accessibility) { this.raise(TSErrors.DuplicateAccessibilityModifier, { at: startLoc, + modifier, }); } else { enforceOrder(startLoc, modifier, modifier, "override"); @@ -327,7 +403,7 @@ export default (superClass: Class): Class => } } else { if (Object.hasOwnProperty.call(modified, modifier)) { - this.raise(TSErrors.DuplicateModifier, { at: startLoc }, modifier); + this.raise(TSErrors.DuplicateModifier, { at: startLoc, modifier }); } else { enforceOrder(startLoc, modifier, "static", "readonly"); enforceOrder(startLoc, modifier, "static", "override"); @@ -341,8 +417,10 @@ export default (superClass: Class): Class => } if (disallowedModifiers?.includes(modifier)) { - // $FlowIgnore - this.raise(errorTemplate, { at: startLoc }, modifier); + this.raise(TSErrors.InvalidModifierOnTypeMember, { + at: startLoc, + modifier, + }); } } } @@ -480,7 +558,12 @@ export default (superClass: Class): Class => this.expect(tt.parenR); if (this.eat(tt.dot)) { - node.qualifier = this.tsParseEntityName(/* allowReservedWords */ true); + // In this instance, the entity name will actually itself be a + // qualifier, so allow it to be a reserved word as well. + node.qualifier = this.tsParseEntityName({ + allowReservedEntityName: true, + allowReservedQualifiers: true, + }); } if (this.match(tt.lt)) { node.typeParameters = this.tsParseTypeArguments(); @@ -488,20 +571,33 @@ export default (superClass: Class): Class => return this.finishNode(node, "TSImportType"); } - tsParseEntityName(allowReservedWords: boolean): N.TsEntityName { - let entity: N.TsEntityName = this.parseIdentifier(); + tsParseEntityName({ + allowReservedEntityName, + allowReservedQualifiers, + }: { + allowReservedEntityName: TokenType | boolean, + allowReservedQualifiers: boolean, + }): N.TsEntityName { + let entity: N.TsEntityName = this.parseIdentifier( + allowReservedEntityName === true || + (allowReservedEntityName !== false && + this.match(allowReservedEntityName)), + ); while (this.eat(tt.dot)) { const node: N.TsQualifiedName = this.startNodeAtNode(entity); node.left = entity; - node.right = this.parseIdentifier(allowReservedWords); + node.right = this.parseIdentifier(allowReservedQualifiers); entity = this.finishNode(node, "TSQualifiedName"); } return entity; } - tsParseTypeReference(): N.TsTypeReference { + tsParseTypeReference(allowConst: boolean = false): N.TsTypeReference { const node: N.TsTypeReference = this.startNode(); - node.typeName = this.tsParseEntityName(/* allowReservedWords */ false); + node.typeName = this.tsParseEntityName({ + allowReservedEntityName: allowConst && tt._const, + allowReservedQualifiers: true, + }); if (!this.hasPrecedingLineBreak() && this.match(tt.lt)) { node.typeParameters = this.tsParseTypeArguments(); } @@ -529,7 +625,10 @@ export default (superClass: Class): Class => if (this.match(tt._import)) { node.exprName = this.tsParseImportType(); } else { - node.exprName = this.tsParseEntityName(/* allowReservedWords */ true); + node.exprName = this.tsParseEntityName({ + allowReservedEntityName: false, + allowReservedQualifiers: true, + }); } return this.finishNode(node, "TSTypeQuery"); } @@ -567,7 +666,7 @@ export default (superClass: Class): Class => refTrailingCommaPos, ); if (node.params.length === 0) { - this.raise(TSErrors.EmptyTypeParameters, { node }); + this.raise(TSErrors.EmptyTypeParameters, { at: node }); } if (refTrailingCommaPos.value !== -1) { this.addExtra(node, "trailingComma", refTrailingCommaPos.value); @@ -576,11 +675,23 @@ export default (superClass: Class): Class => } tsTryNextParseConstantContext(): ?N.TsTypeReference { - if (this.lookahead().type === tt._const) { - this.next(); - return this.tsParseTypeReference(); + if (this.lookahead().type !== tt._const) return null; + + this.next(); + const typeReference = this.tsParseTypeReference(/* allowConst */ true); + + // If the type reference has type parameters, then you are using it as a + // type and not as a const signifier. We'll *never* be able to find this + // name, since const isn't allowed as a type name. So in this instance we + // get to pretend we're the type checker. + if (typeReference.typeParameters) { + this.raise(TSErrors.CannotFindName, { + at: typeReference.typeName, + name: "const", + }); } - return null; + + return typeReference; } // Note: In TypeScript implementation we must provide `yieldContext` and `awaitContext`, @@ -621,11 +732,10 @@ export default (superClass: Class): Class => pattern.type !== "ObjectPattern" && pattern.type !== "ArrayPattern" ) { - this.raise( - TSErrors.UnsupportedSignatureParameterKind, - { node: pattern }, - pattern.type, - ); + this.raise(TSErrors.UnsupportedSignatureParameterKind, { + at: pattern, + type: pattern.type, + }); } return (pattern: any); }, @@ -689,7 +799,7 @@ export default (superClass: Class): Class => if (this.match(tt.parenL) || this.match(tt.lt)) { if (readonly) { - this.raise(TSErrors.ReadonlyForMethodSignature, { node }); + this.raise(TSErrors.ReadonlyForMethodSignature, { at: node }); } const method: N.TsMethodSignature = nodeAny; if (method.kind && this.match(tt.lt)) { @@ -740,7 +850,7 @@ export default (superClass: Class): Class => } if (method[returnTypeKey]) { this.raise(TSErrors.SetAccesorCannotHaveReturnType, { - node: method[returnTypeKey], + at: method[returnTypeKey], }); } } else { @@ -778,10 +888,10 @@ export default (superClass: Class): Class => } } - this.tsParseModifiers( - node, - ["readonly"], - [ + this.tsParseModifiers({ + modified: node, + allowedModifiers: ["readonly"], + disallowedModifiers: [ "declare", "abstract", "private", @@ -790,8 +900,7 @@ export default (superClass: Class): Class => "static", "override", ], - TSErrors.InvalidModifierOnTypeMember, - ); + }); const idx = this.tsTryParseIndexSignature(node); if (idx) { @@ -910,7 +1019,7 @@ export default (superClass: Class): Class => !(type === "TSNamedTupleMember" && elementNode.optional) ) { this.raise(TSErrors.OptionalTypeBeforeRequired, { - node: elementNode, + at: elementNode, }); } @@ -931,7 +1040,7 @@ export default (superClass: Class): Class => labeledElements = labeledElements ?? isLabeled; if (labeledElements !== isLabeled) { this.raise(TSErrors.MixedLabeledAndUnlabeledElements, { - node: elementNode, + at: elementNode, }); } }); @@ -960,7 +1069,7 @@ export default (superClass: Class): Class => ) { labeledNode.label = (type.typeName: N.Identifier); } else { - this.raise(TSErrors.InvalidTupleMemberLabel, { node: type }); + this.raise(TSErrors.InvalidTupleMemberLabel, { at: type }); // This produces an invalid AST, but at least we don't drop // nodes representing the invalid source. // $FlowIgnore @@ -1161,7 +1270,7 @@ export default (superClass: Class): Class => case "TSArrayType": return; default: - this.raise(TSErrors.UnexpectedReadonly, { node }); + this.raise(TSErrors.UnexpectedReadonly, { at: node }); } } @@ -1390,11 +1499,10 @@ export default (superClass: Class): Class => } if (containsEsc) { - this.raise( - Errors.InvalidEscapedReservedWord, - { at: this.state.lastTokStartLoc }, - "asserts", - ); + this.raise(Errors.InvalidEscapedReservedWord, { + at: this.state.lastTokStartLoc, + reservedWord: "asserts", + }); } return true; @@ -1466,7 +1574,7 @@ export default (superClass: Class): Class => } tsParseHeritageClause( - descriptor: string, + token: "extends" | "implements", ): $ReadOnlyArray { const originalStartLoc = this.state.startLoc; @@ -1476,11 +1584,10 @@ export default (superClass: Class): Class => ); if (!delimitedList.length) { - this.raise( - TSErrors.EmptyHeritageClauseType, - { at: originalStartLoc }, - descriptor, - ); + this.raise(TSErrors.EmptyHeritageClauseType, { + at: originalStartLoc, + token, + }); } return delimitedList; @@ -1490,7 +1597,10 @@ export default (superClass: Class): Class => const node: N.TsExpressionWithTypeArguments = this.startNode(); // Note: TS uses parseLeftHandSideExpressionOrHigher, // then has grammar errors later if it's not an EntityName. - node.expression = this.tsParseEntityName(/* allowReservedWords */ false); + node.expression = this.tsParseEntityName({ + allowReservedEntityName: false, + allowReservedQualifiers: false, + }); if (this.match(tt.lt)) { node.typeParameters = this.tsParseTypeArguments(); } @@ -1500,14 +1610,14 @@ export default (superClass: Class): Class => tsParseInterfaceDeclaration( node: N.TsInterfaceDeclaration, - ): N.TsInterfaceDeclaration { + properties: { declare?: true } = {}, + ): ?N.TsInterfaceDeclaration { + if (this.hasFollowingLineBreak()) return null; + this.expectContextual(tt._interface); + if (properties.declare) node.declare = true; if (tokenIsIdentifier(this.state.type)) { node.id = this.parseIdentifier(); - this.checkLVal( - node.id, - "typescript interface declaration", - BIND_TS_INTERFACE, - ); + this.checkIdentifier(node.id, BIND_TS_INTERFACE); } else { node.id = null; this.raise(TSErrors.MissingInterfaceName, { at: this.state.startLoc }); @@ -1527,7 +1637,7 @@ export default (superClass: Class): Class => node: N.TsTypeAliasDeclaration, ): N.TsTypeAliasDeclaration { node.id = this.parseIdentifier(); - this.checkLVal(node.id, "typescript type alias", BIND_TS_TYPE); + this.checkIdentifier(node.id, BIND_TS_TYPE); node.typeParameters = this.tsTryParseTypeParameters(); node.typeAnnotation = this.tsInType(() => { @@ -1607,14 +1717,15 @@ export default (superClass: Class): Class => tsParseEnumDeclaration( node: N.TsEnumDeclaration, - isConst: boolean, + properties: { const?: true, declare?: true } = {}, ): N.TsEnumDeclaration { - if (isConst) node.const = true; + if (properties.const) node.const = true; + if (properties.declare) node.declare = true; + this.expectContextual(tt._enum); node.id = this.parseIdentifier(); - this.checkLVal( + this.checkIdentifier( node.id, - "typescript enum declaration", - isConst ? BIND_TS_CONST_ENUM : BIND_TS_ENUM, + node.const ? BIND_TS_CONST_ENUM : BIND_TS_ENUM, ); this.expect(tt.braceL); @@ -1649,11 +1760,7 @@ export default (superClass: Class): Class => node.id = this.parseIdentifier(); if (!nested) { - this.checkLVal( - node.id, - "module or namespace declaration", - BIND_TS_NAMESPACE, - ); + this.checkIdentifier(node.id, BIND_TS_NAMESPACE); } if (this.eat(tt.dot)) { @@ -1700,7 +1807,7 @@ export default (superClass: Class): Class => ): N.TsImportEqualsDeclaration { node.isExport = isExport || false; node.id = this.parseIdentifier(); - this.checkLVal(node.id, "import equals declaration", BIND_LEXICAL); + this.checkIdentifier(node.id, BIND_LEXICAL); this.expect(tt.eq); const moduleReference = this.tsParseModuleReference(); if ( @@ -1708,7 +1815,7 @@ export default (superClass: Class): Class => moduleReference.type !== "TSExternalModuleReference" ) { this.raise(TSErrors.ImportAliasHasImportType, { - node: moduleReference, + at: moduleReference, }); } node.moduleReference = moduleReference; @@ -1726,7 +1833,10 @@ export default (superClass: Class): Class => tsParseModuleReference(): N.TsModuleReference { return this.tsIsExternalModuleReference() ? this.tsParseExternalModuleReference() - : this.tsParseEntityName(/* allowReservedWords */ false); + : this.tsParseEntityName({ + allowReservedEntityName: false, + allowReservedQualifiers: false, + }); } tsParseExternalModuleReference(): N.TsExternalModuleReference { @@ -1783,45 +1893,61 @@ export default (superClass: Class): Class => } return this.tsInAmbientContext(() => { - switch (starttype) { - case tt._function: - nany.declare = true; - return this.parseFunctionStatement( - nany, - /* async */ false, - /* declarationPosition */ true, - ); - case tt._class: - // While this is also set by tsParseExpressionStatement, we need to set it - // before parsing the class declaration to now how to register it in the scope. + if (starttype === tt._function) { + nany.declare = true; + return this.parseFunctionStatement( + nany, + /* async */ false, + /* declarationPosition */ true, + ); + } + + if (starttype === tt._class) { + // While this is also set by tsParseExpressionStatement, we need to set it + // before parsing the class declaration to know how to register it in the scope. + nany.declare = true; + return this.parseClass( + nany, + /* isStatement */ true, + /* optionalId */ false, + ); + } + + if (starttype === tt._enum) { + return this.tsParseEnumDeclaration(nany, { declare: true }); + } + + if (starttype === tt._global) { + return this.tsParseAmbientExternalModuleDeclaration(nany); + } + + if (starttype === tt._const || starttype === tt._var) { + if (!this.match(tt._const) || !this.isLookaheadContextual("enum")) { nany.declare = true; - return this.parseClass( - nany, - /* isStatement */ true, - /* optionalId */ false, - ); - case tt._const: - if (this.match(tt._const) && this.isLookaheadContextual("enum")) { - // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. - this.expect(tt._const); - this.expectContextual(tt._enum); - return this.tsParseEnumDeclaration(nany, /* isConst */ true); - } - // falls through - case tt._var: - kind = kind || this.state.value; - return this.parseVarStatement(nany, kind); - case tt._global: - return this.tsParseAmbientExternalModuleDeclaration(nany); - default: { - if (tokenIsIdentifier(starttype)) { - return this.tsParseDeclaration( - nany, - this.state.value, - /* next */ true, - ); - } + return this.parseVarStatement(nany, kind || this.state.value, true); } + + // `const enum = 0;` not allowed because "enum" is a strict mode reserved word. + this.expect(tt._const); + return this.tsParseEnumDeclaration(nany, { + const: true, + declare: true, + }); + } + + if (starttype === tt._interface) { + const result = this.tsParseInterfaceDeclaration(nany, { + declare: true, + }); + if (result) return result; + } + + if (tokenIsIdentifier(starttype)) { + return this.tsParseDeclaration( + nany, + this.state.value, + /* next */ true, + ); } }); } @@ -1883,22 +2009,6 @@ export default (superClass: Class): Class => } break; - case "enum": - if (next || tokenIsIdentifier(this.state.type)) { - if (next) this.next(); - return this.tsParseEnumDeclaration(node, /* isConst */ false); - } - break; - - case "interface": - if ( - this.tsCheckLineTerminator(next) && - tokenIsIdentifier(this.state.type) - ) { - return this.tsParseInterfaceDeclaration(node); - } - break; - case "module": if (this.tsCheckLineTerminator(next)) { if (this.match(tt.string)) { @@ -1997,7 +2107,7 @@ export default (superClass: Class): Class => }), ); if (node.params.length === 0) { - this.raise(TSErrors.EmptyTypeArguments, { node }); + this.raise(TSErrors.EmptyTypeArguments, { at: node }); } this.expect(tt.gt); return this.finishNode(node, "TSTypeParameterInstantiation"); @@ -2029,13 +2139,16 @@ export default (superClass: Class): Class => let override = false; if (allowModifiers !== undefined) { const modified = {}; - this.tsParseModifiers(modified, [ - "public", - "private", - "protected", - "override", - "readonly", - ]); + this.tsParseModifiers({ + modified, + allowedModifiers: [ + "public", + "private", + "protected", + "override", + "readonly", + ], + }); accessibility = modified.accessibility; override = modified.override; readonly = modified.readonly; @@ -2059,7 +2172,7 @@ export default (superClass: Class): Class => if (readonly) pp.readonly = readonly; if (override) pp.override = override; if (elt.type !== "Identifier" && elt.type !== "AssignmentPattern") { - this.raise(TSErrors.UnsupportedParameterPropertyKind, { node: pp }); + this.raise(TSErrors.UnsupportedParameterPropertyKind, { at: pp }); } pp.parameter = ((elt: any): N.Identifier | N.AssignmentPattern); return this.finishNode(pp, "TSParameterProperty"); @@ -2072,6 +2185,14 @@ export default (superClass: Class): Class => return elt; } + isSimpleParameter(node) { + return ( + (node.type === "TSParameterProperty" && + super.isSimpleParameter(node.parameter)) || + super.isSimpleParameter(node) + ); + } + parseFunctionBodyAndFinish( node: N.BodilessFunctionOrMethodBase, type: string, @@ -2092,7 +2213,7 @@ export default (superClass: Class): Class => return; } if (bodilessType === "TSDeclareFunction" && this.state.isAmbientContext) { - this.raise(TSErrors.DeclareFunctionHasImplementation, { node }); + this.raise(TSErrors.DeclareFunctionHasImplementation, { at: node }); if ( // $FlowIgnore node.declare @@ -2109,7 +2230,7 @@ export default (superClass: Class): Class => if (!node.body && node.id) { // Function ids are validated after parsing their body. // For bodyless function, we need to do it here. - this.checkLVal(node.id, "function name", BIND_TS_AMBIENT); + this.checkIdentifier(node.id, BIND_TS_AMBIENT); } else { super.registerFunctionStatementId(...arguments); } @@ -2119,7 +2240,7 @@ export default (superClass: Class): Class => items.forEach(node => { if (node?.type === "TSTypeCastExpression") { this.raise(TSErrors.UnexpectedTypeAnnotation, { - node: node.typeAnnotation, + at: node.typeAnnotation, }); } }); @@ -2305,15 +2426,16 @@ export default (superClass: Class): Class => } checkReservedWord( - word: string, // eslint-disable-line no-unused-vars - startLoc: Position, // eslint-disable-line no-unused-vars - checkKeywords: boolean, // eslint-disable-line no-unused-vars - // eslint-disable-next-line no-unused-vars + word: string, + startLoc: Position, + checkKeywords: boolean, isBinding: boolean, ): void { - // Don't bother checking for TypeScript code. // Strict mode words may be allowed as in `declare namespace N { const static: number; }`. // And we have a type checker anyway, so don't bother having the parser do it. + if (!this.state.isAmbientContext) { + super.checkReservedWord(word, startLoc, checkKeywords, isBinding); + } } /* @@ -2366,7 +2488,7 @@ export default (superClass: Class): Class => importNode.specifiers[0].type === "ImportDefaultSpecifier" ) { this.raise(TSErrors.TypeImportCannotSpecifyDefaultAndNamed, { - node: importNode, + at: importNode, }); } @@ -2434,25 +2556,77 @@ export default (superClass: Class): Class => // export default interface allowed in: // https://github.com/Microsoft/TypeScript/pull/16040 if (this.match(tt._interface)) { - const interfaceNode = this.startNode(); - this.next(); - const result = this.tsParseInterfaceDeclaration(interfaceNode); + const result = this.tsParseInterfaceDeclaration(this.startNode()); if (result) return result; } return super.parseExportDefaultExpression(); } - parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement { - if (this.state.type === tt._const) { - const ahead = this.lookahead(); - if (ahead.type === tt._enum) { - const node: N.TsEnumDeclaration = this.startNode(); - this.next(); // eat 'const' - this.expectContextual(tt._enum); - return this.tsParseEnumDeclaration(node, /* isConst */ true); + parseVarStatement( + node: N.VariableDeclaration, + kind: "var" | "let" | "const", + allowMissingInitializer: boolean = false, + ) { + const { isAmbientContext } = this.state; + const declaration = super.parseVarStatement( + node, + kind, + allowMissingInitializer || isAmbientContext, + ); + + if (!isAmbientContext) return declaration; + + for (const { id, init } of declaration.declarations) { + // Empty initializer is the easy case that we want. + if (!init) continue; + + // var and let aren't ever allowed initializers. + // + // If a const declaration has no type annotation and is initiailized to + // a string literal, numeric literal, or enum reference, then it is + // allowed. In an ideal world, we'd check whether init was *actually* an + // enum reference, but we allow anything that "could be" a literal enum + // in `isPossiblyLiteralEnum` since we don't have all the information + // that the typescript compiler has. + if (kind !== "const" || !!id.typeAnnotation) { + this.raise(TSErrors.InitializerNotAllowedInAmbientContext, { + at: init, + }); + } else if ( + init.type !== "StringLiteral" && + init.type !== "BooleanLiteral" && + init.type !== "NumericLiteral" && + init.type !== "BigIntLiteral" && + (init.type !== "TemplateLiteral" || init.expressions.length > 0) && + !isPossiblyLiteralEnum(init) + ) { + this.raise( + TSErrors.ConstInitiailizerMustBeStringOrNumericLiteralOrLiteralEnumReference, + { at: init }, + ); } } + + return declaration; + } + + parseStatementContent(context: ?string, topLevel: ?boolean): N.Statement { + if (this.match(tt._const) && this.isLookaheadContextual("enum")) { + const node: N.TsEnumDeclaration = this.startNode(); + this.expect(tt._const); // eat 'const' + return this.tsParseEnumDeclaration(node, { const: true }); + } + + if (this.isContextual(tt._enum)) { + return this.tsParseEnumDeclaration(this.startNode()); + } + + if (this.isContextual(tt._interface)) { + const result = this.tsParseInterfaceDeclaration(this.startNode()); + if (result) return result; + } + return super.parseStatementContent(context, topLevel); } @@ -2491,13 +2665,11 @@ export default (superClass: Class): Class => "readonly", "static", ]; - this.tsParseModifiers( - member, - modifiers, - /* disallowedModifiers */ undefined, - /* errorTemplate */ undefined, - /* stopOnStartOfClassStaticBlock */ true, - ); + this.tsParseModifiers({ + modified: member, + allowedModifiers: modifiers, + stopOnStartOfClassStaticBlock: true, + }); const callParseClassMemberWithIsStatic = () => { if (this.tsIsStartOfStaticBlocks()) { @@ -2536,20 +2708,19 @@ export default (superClass: Class): Class => classBody.body.push(idx); if ((member: any).abstract) { - this.raise(TSErrors.IndexSignatureHasAbstract, { node: member }); + this.raise(TSErrors.IndexSignatureHasAbstract, { at: member }); } if ((member: any).accessibility) { - this.raise( - TSErrors.IndexSignatureHasAccessibility, - { node: member }, - (member: any).accessibility, - ); + this.raise(TSErrors.IndexSignatureHasAccessibility, { + at: member, + modifier: (member: any).accessibility, + }); } if ((member: any).declare) { - this.raise(TSErrors.IndexSignatureHasDeclare, { node: member }); + this.raise(TSErrors.IndexSignatureHasDeclare, { at: member }); } if ((member: any).override) { - this.raise(TSErrors.IndexSignatureHasOverride, { node: member }); + this.raise(TSErrors.IndexSignatureHasOverride, { at: member }); } return; @@ -2557,13 +2728,13 @@ export default (superClass: Class): Class => if (!this.state.inAbstractClass && (member: any).abstract) { this.raise(TSErrors.NonAbstractClassHasAbstractMethod, { - node: member, + at: member, }); } if ((member: any).override) { if (!state.hadSuperClass) { - this.raise(TSErrors.OverrideNotInSubClass, { node: member }); + this.raise(TSErrors.OverrideNotInSubClass, { at: member }); } } @@ -2579,11 +2750,11 @@ export default (superClass: Class): Class => if (optional) methodOrProp.optional = true; if ((methodOrProp: any).readonly && this.match(tt.parenL)) { - this.raise(TSErrors.ClassMethodHasReadonly, { node: methodOrProp }); + this.raise(TSErrors.ClassMethodHasReadonly, { at: methodOrProp }); } if ((methodOrProp: any).declare && this.match(tt.parenL)) { - this.raise(TSErrors.ClassMethodHasDeclare, { node: methodOrProp }); + this.raise(TSErrors.ClassMethodHasDeclare, { at: methodOrProp }); } } @@ -2674,11 +2845,14 @@ export default (superClass: Class): Class => } parseExportDeclaration(node: N.ExportNamedDeclaration): ?N.Declaration { + if (!this.state.isAmbientContext && this.isContextual(tt._declare)) { + return this.tsInAmbientContext(() => this.parseExportDeclaration(node)); + } + // Store original location/position const startPos = this.state.start; const startLoc = this.state.startLoc; - // "export declare" is equivalent to just "export". const isDeclare = this.eatContextual(tt._declare); if ( @@ -2690,24 +2864,22 @@ export default (superClass: Class): Class => }); } - let declaration: ?N.Declaration; + const isIdentifier = tokenIsIdentifier(this.state.type); + const declaration: ?N.Declaration = + (isIdentifier && this.tsTryParseExportDeclaration()) || + super.parseExportDeclaration(node); + + if (!declaration) return null; - if (tokenIsIdentifier(this.state.type)) { - declaration = this.tsTryParseExportDeclaration(); - } - if (!declaration) { - declaration = super.parseExportDeclaration(node); - } if ( - declaration && - (declaration.type === "TSInterfaceDeclaration" || - declaration.type === "TSTypeAliasDeclaration" || - isDeclare) + declaration.type === "TSInterfaceDeclaration" || + declaration.type === "TSTypeAliasDeclaration" || + isDeclare ) { node.exportKind = "type"; } - if (declaration && isDeclare) { + if (isDeclare) { // Reset location to include `declare` in range this.resetStartLocation(declaration, startPos, startLoc); @@ -2757,13 +2929,13 @@ export default (superClass: Class): Class => } if (node.abstract && this.match(tt.eq)) { const { key } = node; - this.raise( - TSErrors.AbstractPropertyHasInitializer, - { at: this.state.startLoc }, - key.type === "Identifier" && !node.computed - ? key.name - : `[${this.input.slice(key.start, key.end)}]`, - ); + this.raise(TSErrors.AbstractPropertyHasInitializer, { + at: this.state.startLoc, + propertyName: + key.type === "Identifier" && !node.computed + ? key.name + : `[${this.input.slice(key.start, key.end)}]`, + }); } return super.parseClassProperty(node); @@ -2774,16 +2946,15 @@ export default (superClass: Class): Class => ): N.ClassPrivateProperty { // $FlowIgnore if (node.abstract) { - this.raise(TSErrors.PrivateElementHasAbstract, { node }); + this.raise(TSErrors.PrivateElementHasAbstract, { at: node }); } // $FlowIgnore if (node.accessibility) { - this.raise( - TSErrors.PrivateElementHasAccessibility, - { node }, - node.accessibility, - ); + this.raise(TSErrors.PrivateElementHasAccessibility, { + at: node, + modifier: node.accessibility, + }); } this.parseClassPropertyAnnotation(node); @@ -2801,13 +2972,15 @@ export default (superClass: Class): Class => const typeParameters = this.tsTryParseTypeParameters(); if (typeParameters && isConstructor) { this.raise(TSErrors.ConstructorHasTypeParameters, { - node: typeParameters, + at: typeParameters, }); } // $FlowIgnore - if (method.declare && (method.kind === "get" || method.kind === "set")) { - this.raise(TSErrors.DeclareAccessor, { node: method }, method.kind); + const { declare = false, kind } = method; + + if (declare && (kind === "get" || kind === "set")) { + this.raise(TSErrors.DeclareAccessor, { at: method, kind }); } if (typeParameters) method.typeParameters = typeParameters; super.pushClassMethod( @@ -2974,15 +3147,12 @@ export default (superClass: Class): Class => }, state); if (invalidSingleType) { - this.raise( - TSErrors.SingleTypeParameterWithoutTrailingComma, - { - at: createPositionWithColumnOffset(invalidSingleType.loc.end, 1), - }, - process.env.BABEL_8_BREAKING + this.raise(TSErrors.SingleTypeParameterWithoutTrailingComma, { + at: createPositionWithColumnOffset(invalidSingleType.loc.end, 1), + typeParameterName: process.env.BABEL_8_BREAKING ? invalidSingleType.name.name : invalidSingleType.name, - ); + }); } /*:: invariant(arrow.node != null) */ @@ -3040,7 +3210,7 @@ export default (superClass: Class): Class => !node.extra?.trailingComma && this.getPluginOption("typescript", "disallowAmbiguousJSXLike") ) { - this.raise(TSErrors.ReservedArrowTypeParam, { node }); + this.raise(TSErrors.ReservedArrowTypeParam, { at: node }); } } @@ -3085,7 +3255,7 @@ export default (superClass: Class): Class => !this.state.isAmbientContext && !this.state.inType ) { - this.raise(TSErrors.PatternIsOptional, { node: param }); + this.raise(TSErrors.PatternIsOptional, { at: param }); } ((param: any): N.Identifier).optional = true; @@ -3139,41 +3309,28 @@ export default (superClass: Class): Class => } } - checkLVal( - expr: N.Expression, - contextDescription: string, - ...args: - | [BindingTypes | void] - | [BindingTypes | void, ?Set, boolean | void, boolean | void] - ): void { - switch (expr.type) { - case "TSTypeCastExpression": - // Allow "typecasts" to appear on the left of assignment expressions, - // because it may be in an arrow function. - // e.g. `const f = (foo: number = 0) => foo;` - return; - case "TSParameterProperty": - this.checkLVal(expr.parameter, "parameter property", ...args); - return; - case "TSAsExpression": - case "TSTypeAssertion": - if ( - /*bindingType*/ !args[0] && - contextDescription !== "parenthesized expression" && - !expr.extra?.parenthesized - ) { - this.raise(Errors.InvalidLhs, { node: expr }, contextDescription); - break; - } - this.checkLVal(expr.expression, "parenthesized expression", ...args); - return; - case "TSNonNullExpression": - this.checkLVal(expr.expression, contextDescription, ...args); - return; - default: - super.checkLVal(expr, contextDescription, ...args); - return; - } + isValidLVal(type: string, isParenthesized: boolean, binding: BindingTypes) { + return ( + getOwn( + { + // Allow "typecasts" to appear on the left of assignment expressions, + // because it may be in an arrow function. + // e.g. `const f = (foo: number = 0) => foo;` + TSTypeCastExpression: true, + TSParameterProperty: "parameter", + TSNonNullExpression: "expression", + TSAsExpression: (binding !== BIND_NONE || isParenthesized) && [ + "expression", + true, + ], + TSTypeAssertion: (binding !== BIND_NONE || isParenthesized) && [ + "expression", + true, + ], + }, + type, + ) || super.isValidLVal(type, isParenthesized, binding) + ); } parseBindingAtom(): N.Pattern { @@ -3240,7 +3397,7 @@ export default (superClass: Class): Class => node.right.start < node.typeAnnotation.start ) { this.raise(TSErrors.TypeAnnotationAfterAssign, { - node: node.typeAnnotation, + at: node.typeAnnotation, }); } @@ -3296,7 +3453,7 @@ export default (superClass: Class): Class => exprList[i] = this.typeCastToParameter(expr); } else { this.raise(TSErrors.UnexpectedTypeCastInParameter, { - node: expr, + at: expr, }); } break; @@ -3387,7 +3544,7 @@ export default (superClass: Class): Class => tsParseAbstractDeclaration( node: any, - ): N.ClassDeclaration | N.TsInterfaceDeclaration | typeof undefined { + ): N.ClassDeclaration | ?N.TsInterfaceDeclaration { if (this.match(tt._class)) { node.abstract = true; return this.parseClass( @@ -3404,9 +3561,8 @@ export default (superClass: Class): Class => if (!this.hasFollowingLineBreak()) { node.abstract = true; this.raise(TSErrors.NonClassMethodPropertyHasAbstractModifer, { - node, + at: node, }); - this.next(); return this.tsParseInterfaceDeclaration( (node: N.TsInterfaceDeclaration), ); @@ -3424,13 +3580,13 @@ export default (superClass: Class): Class => : !!method.body; if (hasBody) { const { key } = method; - this.raise( - TSErrors.AbstractMethodHasImplementation, - { node: method }, - key.type === "Identifier" && !method.computed - ? key.name - : `[${this.input.slice(key.start, key.end)}]`, - ); + this.raise(TSErrors.AbstractMethodHasImplementation, { + at: method, + methodName: + key.type === "Identifier" && !method.computed + ? key.name + : `[${this.input.slice(key.start, key.end)}]`, + }); } } return method; @@ -3536,7 +3692,9 @@ export default (superClass: Class): Class => // { type as as something } hasTypeSpecifier = true; leftOfAs = firstAs; - rightOfAs = this.parseIdentifier(); + rightOfAs = isImport + ? this.parseIdentifier() + : this.parseModuleExportName(); canParseAsKeyword = false; } else { // { type as as } @@ -3546,7 +3704,9 @@ export default (superClass: Class): Class => } else if (tokenIsKeywordOrIdentifier(this.state.type)) { // { type as something } canParseAsKeyword = false; - rightOfAs = this.parseIdentifier(); + rightOfAs = isImport + ? this.parseIdentifier() + : this.parseModuleExportName(); } else { // { type as } hasTypeSpecifier = true; @@ -3555,7 +3715,9 @@ export default (superClass: Class): Class => } else if (tokenIsKeywordOrIdentifier(this.state.type)) { // { type something ...? } hasTypeSpecifier = true; - leftOfAs = this.parseIdentifier(); + leftOfAs = isImport + ? this.parseIdentifier() + : this.parseModuleExportName(); } if (hasTypeSpecifier && isInTypeOnlyImportExport) { this.raise( @@ -3581,7 +3743,31 @@ export default (superClass: Class): Class => node[rightOfAsKey] = cloneIdentifier(node[leftOfAsKey]); } if (isImport) { - this.checkLVal(node[rightOfAsKey], "import specifier", BIND_LEXICAL); + this.checkIdentifier(node[rightOfAsKey], BIND_LEXICAL); } } }; + +function isPossiblyLiteralEnum(expression: N.Expression): boolean { + if (expression.type !== "MemberExpression") return false; + + const { computed, property } = expression; + + if ( + computed && + property.type !== "StringLiteral" && + (property.type !== "TemplateLiteral" || property.expressions.length > 0) + ) { + return false; + } + + return isUncomputedMemberExpressionChain(expression.object); +} + +function isUncomputedMemberExpressionChain(expression: N.Expression): boolean { + if (expression.type === "Identifier") return true; + if (expression.type !== "MemberExpression") return false; + if (expression.computed) return false; + + return isUncomputedMemberExpressionChain(expression.object); +} diff --git a/packages/babel-parser/src/tokenizer/index.js b/packages/babel-parser/src/tokenizer/index.js index 30fe64e00e92..779fabd7b1f5 100644 --- a/packages/babel-parser/src/tokenizer/index.js +++ b/packages/babel-parser/src/tokenizer/index.js @@ -3,7 +3,12 @@ /*:: declare var invariant; */ import type { Options } from "../options"; -import { Position, createPositionWithColumnOffset } from "../util/location"; +import { + Position, + SourceLocation, + createPositionWithColumnOffset, +} from "../util/location"; +import CommentsParser from "../parser/comments"; import * as N from "../types"; import * as charCodes from "charcodes"; import { isIdentifierStart, isIdentifierChar } from "../util/identifier"; @@ -15,8 +20,12 @@ import { type TokenType, } from "./types"; import { type TokContext } from "./context"; -import ParserErrors, { Errors, type ErrorTemplate } from "../parser/error"; -import { SourceLocation } from "../util/location"; +import { + Errors, + type ParseError, + type ParseErrorConstructor, + type RaiseProperties, +} from "../parse-error"; import { lineBreakG, isNewLine, @@ -24,7 +33,7 @@ import { skipWhiteSpace, } from "../util/whitespace"; import State from "./state"; -import type { LookaheadState } from "./state"; +import type { LookaheadState, DeferredStrictError } from "./state"; const VALID_REGEX_FLAGS = new Set([ charCodes.lowercaseG, @@ -126,15 +135,7 @@ export class Token { // ## Tokenizer -export default class Tokenizer extends ParserErrors { - // Forward-declarations - // parser/util.js - /*:: - +hasPrecedingLineBreak: () => boolean; - +unexpected: (loc?: ?Position, type?: TokenType) => empty; - +expectPlugin: (name: string, loc?: Position) => true; - */ - +export default class Tokenizer extends CommentsParser { isLookahead: boolean; // Token store. @@ -286,9 +287,8 @@ export default class Tokenizer extends ParserErrors { // after a "use strict" directive. Strict mode will be set at parse // time for any literals that occur after the next node of the strict // directive. - this.state.strictErrors.forEach(({ message, loc }) => - /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(message, { at: loc }), + this.state.strictErrors.forEach(([toParseError, at]) => + this.raise(toParseError, { at }), ); this.state.strictErrors.clear(); } @@ -1071,11 +1071,10 @@ export default class Tokenizer extends ParserErrors { } } - throw this.raise( - Errors.InvalidOrUnexpectedToken, - { at: this.state.curPosition() }, - String.fromCodePoint(code), - ); + throw this.raise(Errors.InvalidOrUnexpectedToken, { + at: this.state.curPosition(), + unexpected: String.fromCodePoint(code), + }); } finishOp(type: TokenType, size: number): void { @@ -1240,11 +1239,10 @@ export default class Tokenizer extends ParserErrors { if (this.options.errorRecovery && val <= 9) { val = 0; - this.raise( - Errors.InvalidDigit, - { at: this.state.curPosition() }, + this.raise(Errors.InvalidDigit, { + at: this.state.curPosition(), radix, - ); + }); } else if (forceLen) { val = 0; invalid = true; @@ -1273,12 +1271,11 @@ export default class Tokenizer extends ParserErrors { this.state.pos += 2; // 0x const val = this.readInt(radix); if (val == null) { - this.raise( - Errors.InvalidDigit, + this.raise(Errors.InvalidDigit, { // Numeric literals can't have newlines, so this is safe to do. - { at: createPositionWithColumnOffset(startLoc, 2) }, + at: createPositionWithColumnOffset(startLoc, 2), radix, - ); + }); } const next = this.input.charCodeAt(this.state.pos); @@ -1326,7 +1323,7 @@ export default class Tokenizer extends ParserErrors { if (hasLeadingZero) { const integer = this.input.slice(start, this.state.pos); - this.recordStrictModeErrors(Errors.StrictOctalLiteral, startLoc); + this.recordStrictModeErrors(Errors.StrictOctalLiteral, { at: startLoc }); if (!this.state.strict) { // disallow numeric separators in non octal decimals and legacy octal likes const underscorePos = integer.indexOf("_"); @@ -1541,11 +1538,16 @@ export default class Tokenizer extends ParserErrors { } } - recordStrictModeErrors(message: ErrorTemplate, loc: Position) { - if (this.state.strict && !this.state.strictErrors.has(loc.index)) { - this.raise(message, { at: loc }); + recordStrictModeErrors( + toParseError: DeferredStrictError, + { at }: { at: Position }, + ) { + const index = at.index; + + if (this.state.strict && !this.state.strictErrors.has(index)) { + this.raise(toParseError, { at }); } else { - this.state.strictErrors.set(loc.index, { loc, message }); + this.state.strictErrors.set(index, [toParseError, at]); } } @@ -1592,12 +1594,11 @@ export default class Tokenizer extends ParserErrors { if (inTemplate) { return null; } else { - this.recordStrictModeErrors( - Errors.StrictNumericEscape, + this.recordStrictModeErrors(Errors.StrictNumericEscape, { // We immediately follow a "\\", and we're an 8 or a 9, so we must // be on the same line. - createPositionWithColumnOffset(this.state.curPosition(), -1), - ); + at: createPositionWithColumnOffset(this.state.curPosition(), -1), + }); } // fall through default: @@ -1631,7 +1632,9 @@ export default class Tokenizer extends ParserErrors { if (inTemplate) { return null; } else { - this.recordStrictModeErrors(Errors.StrictNumericEscape, codePos); + this.recordStrictModeErrors(Errors.StrictNumericEscape, { + at: codePos, + }); } } @@ -1734,15 +1737,101 @@ export default class Tokenizer extends ParserErrors { checkKeywordEscapes(): void { const { type } = this.state; if (tokenIsKeyword(type) && this.state.containsEsc) { - this.raise( - Errors.InvalidEscapedReservedWord, - { at: this.state.startLoc }, - tokenLabelName(type), - ); + this.raise(Errors.InvalidEscapedReservedWord, { + at: this.state.startLoc, + reservedWord: tokenLabelName(type), + }); + } + } + + /** + * Raise a `ParseError` given the appropriate properties. If passed a + * `Position` for the `at` property, raises the `ParseError` at that location. + * Otherwise, if passed a `Node`, raises the `ParseError` at the start + * location of that `Node`. + * + * If `errorRecovery` is `true`, the error is pushed to the errors array and + * returned. If `errorRecovery` is `false`, the error is instead thrown. + * + * @param {Class>>} ParseErrorClass + * @param {RaiseProperties} raiseProperties + * @returns {(ParseError | empty)} + * @memberof Tokenizer + */ + raise( + toParseError: ParseErrorConstructor, + raiseProperties: RaiseProperties, + ): ParseError { + const { at, ...details } = raiseProperties; + const loc = at instanceof Position ? at : at.loc.start; + const error = toParseError({ loc, details }); + + if (!this.options.errorRecovery) throw error; + if (!this.isLookahead) this.state.errors.push(error); + + return error; + } + + /** + * If `errorRecovery` is `false`, this method behaves identically to `raise`. + * If `errorRecovery` is `true`, this method will first see if there is + * already an error stored at the same `Position`, and replaces it with the + * one generated here. + * + * @param {Class>>} ParseErrorClass + * @param {RaiseProperties} raiseProperties + * @returns {(ParseError | empty)} + * @memberof Tokenizer + */ + raiseOverwrite( + toParseError: ParseErrorConstructor, + raiseProperties: RaiseProperties, + ): ParseError | empty { + const { at, ...details } = raiseProperties; + const loc = at instanceof Position ? at : at.loc.start; + const pos = loc.index; + const errors = this.state.errors; + + for (let i = errors.length - 1; i >= 0; i--) { + const error = errors[i]; + if (error.loc.index === pos) { + return (errors[i] = toParseError({ loc, details })); + } + if (error.loc.index < pos) break; } + + return this.raise(toParseError, raiseProperties); } // updateContext is used by the jsx plugin // eslint-disable-next-line no-unused-vars updateContext(prevType: TokenType): void {} + + // Raise an unexpected token error. Can take the expected token type. + unexpected(loc?: Position | null, type?: TokenType): void { + throw this.raise(Errors.UnexpectedToken, { + expected: type ? tokenLabelName(type) : null, + at: loc != null ? loc : this.state.startLoc, + }); + } + + expectPlugin(pluginName: string, loc?: Position): true { + if (this.hasPlugin(pluginName)) { + return true; + } + + throw this.raise(Errors.MissingPlugin, { + at: loc != null ? loc : this.state.startLoc, + missingPlugin: [pluginName], + }); + } + + expectOnePlugin(pluginNames: string[]): void { + if (!pluginNames.some(name => this.hasPlugin(name))) { + throw this.raise(Errors.MissingOneOfPlugins, { + at: this.state.startLoc, + missingPlugin: pluginNames, + }); + } + } } diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index ac9a04187e7a..692da2c1c591 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -7,7 +7,11 @@ import { Position } from "../util/location"; import { types as ct, type TokContext } from "./context"; import { tt, type TokenType } from "./types"; -import type { ErrorData, ParsingError } from "../parser/error"; +import { Errors, type ParseError } from "../parse-error"; + +export type DeferredStrictError = + | typeof Errors.StrictNumericEscape + | typeof Errors.StrictOctalLiteral; type TopicContextState = { // When a topic binding has been currently established, @@ -45,7 +49,7 @@ export default class State { this.startLoc = this.endLoc = new Position(startLine, startColumn, 0); } - errors: ParsingError[] = []; + errors: ParseError[] = []; // Used to signify the start of a potential arrow function potentialArrowAt: number = -1; @@ -140,7 +144,7 @@ export default class State { // todo(JLHwung): set strictErrors to null and avoid recording string errors // after a non-directive is parsed - strictErrors: Map = new Map(); + strictErrors: Map = new Map(); // Tokens length in token store tokensLength: number = 0; diff --git a/packages/babel-parser/src/types.js b/packages/babel-parser/src/types.js index 5fb402aadbbb..e6b53e381722 100644 --- a/packages/babel-parser/src/types.js +++ b/packages/babel-parser/src/types.js @@ -4,7 +4,7 @@ import type { SourceType } from "./options"; import type { Token } from "./tokenizer"; import type { SourceLocation } from "./util/location"; import type { PlaceholderTypes } from "./plugins/placeholders"; -import type { ParsingError } from "./parser/error"; +import type { ParseError } from "./parse-error"; /* * If making any changes to the AST, update: @@ -159,7 +159,7 @@ export type DecimalLiteral = NodeBase & { export type ParserOutput = { comments: $ReadOnlyArray, - errors: Array, + errors: Array>, tokens?: $ReadOnlyArray, }; // Programs diff --git a/packages/babel-parser/src/util/class-scope.js b/packages/babel-parser/src/util/class-scope.js index db2c95273859..41fc5e87dddb 100644 --- a/packages/babel-parser/src/util/class-scope.js +++ b/packages/babel-parser/src/util/class-scope.js @@ -6,7 +6,8 @@ import { type ClassElementTypes, } from "./scopeflags"; import { Position } from "./location"; -import { Errors, type raiseFunction } from "../parser/error"; +import { Errors } from "../parse-error"; +import Tokenizer from "../tokenizer"; export class ClassScope { // A list of private named declared in the current class @@ -21,12 +22,12 @@ export class ClassScope { } export default class ClassScopeHandler { + parser: Tokenizer; stack: Array = []; - declare raise: raiseFunction; undefinedPrivateNames: Map = new Map(); - constructor(raise: raiseFunction) { - this.raise = raise; + constructor(parser: Tokenizer) { + this.parser = parser; } current(): ClassScope { @@ -52,7 +53,10 @@ export default class ClassScopeHandler { current.undefinedPrivateNames.set(name, loc); } } else { - this.raise(Errors.InvalidPrivateFieldResolution, { at: loc }, name); + this.parser.raise(Errors.InvalidPrivateFieldResolution, { + at: loc, + identifierName: name, + }); } } } @@ -87,7 +91,10 @@ export default class ClassScopeHandler { } if (redefined) { - this.raise(Errors.PrivateNameRedeclaration, { at: loc }, name); + this.parser.raise(Errors.PrivateNameRedeclaration, { + at: loc, + identifierName: name, + }); } privateNames.add(name); @@ -104,7 +111,10 @@ export default class ClassScopeHandler { classScope.undefinedPrivateNames.set(name, loc); } else { // top-level - this.raise(Errors.InvalidPrivateFieldResolution, { at: loc }, name); + this.parser.raise(Errors.InvalidPrivateFieldResolution, { + at: loc, + identifierName: name, + }); } } } diff --git a/packages/babel-parser/src/util/expression-scope.js b/packages/babel-parser/src/util/expression-scope.js index 2cc38db8c121..c38394f0b6ac 100644 --- a/packages/babel-parser/src/util/expression-scope.js +++ b/packages/babel-parser/src/util/expression-scope.js @@ -1,7 +1,9 @@ // @flow -import type { ErrorData, ErrorTemplate, raiseFunction } from "../parser/error"; +import { Errors } from "../parse-error"; import { Position } from "./location"; +import type { Node } from "../types"; +import Tokenizer from "../tokenizer"; /*:: declare var invariant; */ /** @@ -17,7 +19,7 @@ ExpressionScope is used to track declaration errors in these ambiguous patterns: e.g. we don't know if `async({ x })` is a call expression or an async arrow function parameters until we see an `=>` after `)` -The following declaration errors (@see parser/error-message) will be recorded in +The following declaration errors (@see parser-errors/standard) will be recorded in some expression scopes and thrown later when we know what the ambigous pattern is - AwaitBindingIdentifier @@ -74,27 +76,45 @@ class ExpressionScope { } } +type ArrowHeadParsingParameterInitializerError = + | typeof Errors.AwaitExpressionFormalParameter + | typeof Errors.YieldInParameter; + +type ArrowHeadParsingDeclarationError = + | ArrowHeadParsingParameterInitializerError + | typeof Errors.InvalidParenthesizedAssignment + | typeof Errors.AwaitBindingIdentifier; + class ArrowHeadParsingScope extends ExpressionScope { - errors: Map = new Map(); + declarationErrors: Map = + new Map(); constructor(type: 1 | 2) { super(type); } - recordDeclarationError(message: ErrorTemplate, loc: Position) { - this.errors.set(loc.index, { message, loc }); + recordDeclarationError( + ParsingErrorClass: T, + { at }: { at: Position }, + ) { + const index = at.index; + + this.declarationErrors.set(index, [ParsingErrorClass, at]); } - clearDeclarationError(loc: Position) { - this.errors.delete(loc.index); + clearDeclarationError(index: number) { + this.declarationErrors.delete(index); } - iterateErrors(iterator: (data: ErrorData) => void) { - this.errors.forEach(iterator); + iterateErrors( + iterator: ([ArrowHeadParsingDeclarationError, Position]) => void, + ) { + this.declarationErrors.forEach(iterator); } } export default class ExpressionScopeHandler { + parser: Tokenizer; stack: Array = [new ExpressionScope()]; - declare raise: raiseFunction; - constructor(raise: raiseFunction) { - this.raise = raise; + + constructor(parser: Tokenizer) { + this.parser = parser; } enter(scope: ExpressionScope) { this.stack.push(scope); @@ -115,16 +135,17 @@ export default class ExpressionScopeHandler { * @memberof ExpressionScopeHandler */ recordParameterInitializerError( - loc: Position, - template: ErrorTemplate, + toParseError: ArrowHeadParsingParameterInitializerError, + { at: node }: { at: Node }, ): void { + const origin = { at: node.loc.start }; const { stack } = this; let i = stack.length - 1; let scope: ExpressionScope = stack[i]; while (!scope.isCertainlyParameterDeclaration()) { if (scope.canBeArrowParameterDeclaration()) { /*:: invariant(scope instanceof ArrowHeadParsingScope) */ - scope.recordDeclarationError(template, loc); + scope.recordDeclarationError(toParseError, origin); } else { /*:: invariant(scope.type == kExpression) */ // Type-Expression is the boundary where initializer error can populate to @@ -132,8 +153,7 @@ export default class ExpressionScopeHandler { } scope = stack[--i]; } - /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(template, { at: loc }); + this.parser.raise(toParseError, origin); } /** @@ -157,17 +177,18 @@ export default class ExpressionScopeHandler { * @returns {void} * @memberof ExpressionScopeHandler */ - recordParenthesizedIdentifierError( - template: ErrorTemplate, - loc: Position, - ): void { + recordParenthesizedIdentifierError({ at: node }: { at: Node }): void { const { stack } = this; const scope: ExpressionScope = stack[stack.length - 1]; + const origin = { at: node.loc.start }; if (scope.isCertainlyParameterDeclaration()) { - this.raise(template, { at: loc }); + this.parser.raise(Errors.InvalidParenthesizedAssignment, origin); } else if (scope.canBeArrowParameterDeclaration()) { /*:: invariant(scope instanceof ArrowHeadParsingScope) */ - scope.recordDeclarationError(template, loc); + scope.recordDeclarationError( + Errors.InvalidParenthesizedAssignment, + origin, + ); } else { return; } @@ -182,17 +203,14 @@ export default class ExpressionScopeHandler { * @param {ErrorTemplate} template * @memberof ExpressionScopeHandler */ - recordAsyncArrowParametersError( - template: ErrorTemplate, - loc: Position, - ): void { + recordAsyncArrowParametersError({ at }: { at: Position }): void { const { stack } = this; let i = stack.length - 1; let scope: ExpressionScope = stack[i]; while (scope.canBeArrowParameterDeclaration()) { if (scope.type === kMaybeAsyncArrowParameterDeclaration) { /*:: invariant(scope instanceof ArrowHeadParsingScope) */ - scope.recordDeclarationError(template, loc); + scope.recordDeclarationError(Errors.AwaitBindingIdentifier, { at }); } scope = stack[--i]; } @@ -203,15 +221,14 @@ export default class ExpressionScopeHandler { const currentScope = stack[stack.length - 1]; if (!currentScope.canBeArrowParameterDeclaration()) return; /*:: invariant(currentScope instanceof ArrowHeadParsingScope) */ - currentScope.iterateErrors(({ message, loc }) => { - /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(message, { at: loc }); + currentScope.iterateErrors(([toParseError, loc]) => { + this.parser.raise(toParseError, { at: loc }); // iterate from parent scope let i = stack.length - 2; let scope = stack[i]; while (scope.canBeArrowParameterDeclaration()) { /*:: invariant(scope instanceof ArrowHeadParsingScope) */ - scope.clearDeclarationError(loc); + scope.clearDeclarationError(loc.index); scope = stack[--i]; } }); diff --git a/packages/babel-parser/src/util/scope.js b/packages/babel-parser/src/util/scope.js index 573fb50228b2..5547699ba23c 100644 --- a/packages/babel-parser/src/util/scope.js +++ b/packages/babel-parser/src/util/scope.js @@ -18,7 +18,8 @@ import { } from "./scopeflags"; import { Position } from "./location"; import * as N from "../types"; -import { Errors, type raiseFunction } from "../parser/error"; +import { Errors } from "../parse-error"; +import Tokenizer from "../tokenizer"; // Start an AST node, attaching a start offset. export class Scope { @@ -38,13 +39,13 @@ export class Scope { // The functions in this module keep track of declared variables in the // current scope in order to detect duplicate variable names. export default class ScopeHandler { + parser: Tokenizer; scopeStack: Array = []; - declare raise: raiseFunction; - declare inModule: boolean; + inModule: boolean; undefinedExports: Map = new Map(); - constructor(raise: raiseFunction, inModule: boolean) { - this.raise = raise; + constructor(parser: Tokenizer, inModule: boolean) { + this.parser = parser; this.inModule = inModule; } @@ -103,7 +104,7 @@ export default class ScopeHandler { treatFunctionsAsVarInScope(scope: IScope): boolean { return !!( scope.flags & SCOPE_FUNCTION || - (!this.inModule && scope.flags & SCOPE_PROGRAM) + (!this.parser.inModule && scope.flags & SCOPE_PROGRAM) ); } @@ -131,13 +132,13 @@ export default class ScopeHandler { if (scope.flags & SCOPE_VAR) break; } } - if (this.inModule && scope.flags & SCOPE_PROGRAM) { + if (this.parser.inModule && scope.flags & SCOPE_PROGRAM) { this.undefinedExports.delete(name); } } maybeExportDefined(scope: IScope, name: string) { - if (this.inModule && scope.flags & SCOPE_PROGRAM) { + if (this.parser.inModule && scope.flags & SCOPE_PROGRAM) { this.undefinedExports.delete(name); } } @@ -149,7 +150,10 @@ export default class ScopeHandler { loc: Position, ) { if (this.isRedeclaredInScope(scope, name, bindingType)) { - this.raise(Errors.VarRedeclaration, { at: loc }, name); + this.parser.raise(Errors.VarRedeclaration, { + at: loc, + identifierName: name, + }); } } diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-6/input.js b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-6/input.js new file mode 100644 index 000000000000..b87d4aead5d1 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-6/input.js @@ -0,0 +1 @@ +(a += 1) = t \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-6/output.json b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-6/output.json new file mode 100644 index 000000000000..b924fb6f6ed4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-6/output.json @@ -0,0 +1,53 @@ +{ + "type": "File", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "errors": [ + "SyntaxError: Invalid parenthesized assignment pattern. (1:1)", + "SyntaxError: Only '=' operator can be used for specifying default value. (1:2)" + ], + "program": { + "type": "Program", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "operator": "=", + "left": { + "type": "AssignmentPattern", + "start":1,"end":7,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":7,"index":7}}, + "left": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"a"}, + "name": "a" + }, + "right": { + "type": "NumericLiteral", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":7,"index":7}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "extra": { + "parenthesized": true, + "parenStart": 0 + } + }, + "right": { + "type": "Identifier", + "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12},"identifierName":"t"}, + "name": "t" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-7/input.js b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-7/input.js new file mode 100644 index 000000000000..72749bdebdd6 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-7/input.js @@ -0,0 +1 @@ +(a -= 1) = t \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-7/output.json b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-7/output.json new file mode 100644 index 000000000000..b924fb6f6ed4 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-7/output.json @@ -0,0 +1,53 @@ +{ + "type": "File", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "errors": [ + "SyntaxError: Invalid parenthesized assignment pattern. (1:1)", + "SyntaxError: Only '=' operator can be used for specifying default value. (1:2)" + ], + "program": { + "type": "Program", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":12,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":12,"index":12}}, + "operator": "=", + "left": { + "type": "AssignmentPattern", + "start":1,"end":7,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":7,"index":7}}, + "left": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"a"}, + "name": "a" + }, + "right": { + "type": "NumericLiteral", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":7,"index":7}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "extra": { + "parenthesized": true, + "parenStart": 0 + } + }, + "right": { + "type": "Identifier", + "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12},"identifierName":"t"}, + "name": "t" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-8/input.js b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-8/input.js new file mode 100644 index 000000000000..b68d94e47c7a --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-8/input.js @@ -0,0 +1 @@ +(b = (a -= 1)) = t \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-8/output.json b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-8/output.json new file mode 100644 index 000000000000..0af83cf4d603 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-assignment-pattern-8/output.json @@ -0,0 +1,66 @@ +{ + "type": "File", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "errors": [ + "SyntaxError: Invalid parenthesized assignment pattern. (1:1)" + ], + "program": { + "type": "Program", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "operator": "=", + "left": { + "type": "AssignmentPattern", + "start":1,"end":13,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":13,"index":13}}, + "left": { + "type": "Identifier", + "start":1,"end":2,"loc":{"start":{"line":1,"column":1,"index":1},"end":{"line":1,"column":2,"index":2},"identifierName":"b"}, + "name": "b" + }, + "right": { + "type": "AssignmentExpression", + "start":6,"end":12,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":12,"index":12}}, + "operator": "-=", + "left": { + "type": "Identifier", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":7,"index":7},"identifierName":"a"}, + "name": "a" + }, + "right": { + "type": "NumericLiteral", + "start":11,"end":12,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":12,"index":12}}, + "extra": { + "rawValue": 1, + "raw": "1" + }, + "value": 1 + }, + "extra": { + "parenthesized": true, + "parenStart": 5 + } + }, + "extra": { + "parenthesized": true, + "parenStart": 0 + } + }, + "right": { + "type": "Identifier", + "start":17,"end":18,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":18,"index":18},"identifierName":"t"}, + "name": "t" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-postfix-operation/input.js b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-postfix-operation/input.js new file mode 100644 index 000000000000..4316929d0fa8 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-postfix-operation/input.js @@ -0,0 +1 @@ +a++ = t \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-postfix-operation/output.json b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-postfix-operation/output.json new file mode 100644 index 000000000000..5534fdbb0550 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-postfix-operation/output.json @@ -0,0 +1,41 @@ +{ + "type": "File", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "errors": [ + "SyntaxError: Invalid left-hand side in assignment expression. (1:0)" + ], + "program": { + "type": "Program", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "operator": "=", + "left": { + "type": "UpdateExpression", + "start":0,"end":3,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":3,"index":3}}, + "operator": "++", + "prefix": false, + "argument": { + "type": "Identifier", + "start":0,"end":1,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":1,"index":1},"identifierName":"a"}, + "name": "a" + } + }, + "right": { + "type": "Identifier", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":7,"index":7},"identifierName":"t"}, + "name": "t" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-prefix-operation/input.js b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-prefix-operation/input.js new file mode 100644 index 000000000000..3e635e2e1733 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-prefix-operation/input.js @@ -0,0 +1 @@ +++a = t \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-prefix-operation/output.json b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-prefix-operation/output.json new file mode 100644 index 000000000000..583c0a9ffab2 --- /dev/null +++ b/packages/babel-parser/test/fixtures/core/categorized/invalid-left-hand-side-in-prefix-operation/output.json @@ -0,0 +1,41 @@ +{ + "type": "File", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "errors": [ + "SyntaxError: Invalid left-hand side in assignment expression. (1:0)" + ], + "program": { + "type": "Program", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "ExpressionStatement", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "expression": { + "type": "AssignmentExpression", + "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, + "operator": "=", + "left": { + "type": "UpdateExpression", + "start":0,"end":3,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":3,"index":3}}, + "operator": "++", + "prefix": true, + "argument": { + "type": "Identifier", + "start":2,"end":3,"loc":{"start":{"line":1,"column":2,"index":2},"end":{"line":1,"column":3,"index":3},"identifierName":"a"}, + "name": "a" + } + }, + "right": { + "type": "Identifier", + "start":6,"end":7,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":7,"index":7},"identifierName":"t"}, + "name": "t" + } + } + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/core/uncategorised/536/output.json b/packages/babel-parser/test/fixtures/core/uncategorised/536/output.json index cb7ad447ddcd..cc71c56d59f9 100644 --- a/packages/babel-parser/test/fixtures/core/uncategorised/536/output.json +++ b/packages/babel-parser/test/fixtures/core/uncategorised/536/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":8,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":8,"index":8}}, "errors": [ - "SyntaxError: 'Const declarations' require an initialization value. (1:7)" + "SyntaxError: Missing initializer in const declaration. (1:7)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-10/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-10/input.js new file mode 100644 index 000000000000..9367b5d6ac80 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-10/input.js @@ -0,0 +1 @@ +const [let = 10] = []; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-10/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-10/output.json new file mode 100644 index 000000000000..28f4b76d6588 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-10/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "errors": [ + "SyntaxError: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. (1:7)" + ], + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":6,"end":21,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":21,"index":21}}, + "id": { + "type": "ArrayPattern", + "start":6,"end":16,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":16,"index":16}}, + "elements": [ + { + "type": "AssignmentPattern", + "start":7,"end":15,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":15,"index":15}}, + "left": { + "type": "Identifier", + "start":7,"end":10,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":10,"index":10},"identifierName":"let"}, + "name": "let" + }, + "right": { + "type": "NumericLiteral", + "start":13,"end":15,"loc":{"start":{"line":1,"column":13,"index":13},"end":{"line":1,"column":15,"index":15}}, + "extra": { + "rawValue": 10, + "raw": "10" + }, + "value": 10 + } + } + ] + }, + "init": { + "type": "ArrayExpression", + "start":19,"end":21,"loc":{"start":{"line":1,"column":19,"index":19},"end":{"line":1,"column":21,"index":21}}, + "elements": [] + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-11/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-11/input.js new file mode 100644 index 000000000000..2b523cbca1d1 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-11/input.js @@ -0,0 +1 @@ +let [...let] = []; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-11/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-11/output.json new file mode 100644 index 000000000000..f15724629df5 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-11/output.json @@ -0,0 +1,47 @@ +{ + "type": "File", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "errors": [ + "SyntaxError: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. (1:8)" + ], + "program": { + "type": "Program", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":17,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":17,"index":17}}, + "id": { + "type": "ArrayPattern", + "start":4,"end":12,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":12,"index":12}}, + "elements": [ + { + "type": "RestElement", + "start":5,"end":11,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":11,"index":11}}, + "argument": { + "type": "Identifier", + "start":8,"end":11,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":11,"index":11},"identifierName":"let"}, + "name": "let" + } + } + ] + }, + "init": { + "type": "ArrayExpression", + "start":15,"end":17,"loc":{"start":{"line":1,"column":15,"index":15},"end":{"line":1,"column":17,"index":17}}, + "elements": [] + } + } + ], + "kind": "let" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-12/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-12/input.js new file mode 100644 index 000000000000..d0356c49fe26 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-12/input.js @@ -0,0 +1 @@ +const [...let] = []; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-12/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-12/output.json new file mode 100644 index 000000000000..343ee3a4e706 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-12/output.json @@ -0,0 +1,47 @@ +{ + "type": "File", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}}, + "errors": [ + "SyntaxError: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. (1:10)" + ], + "program": { + "type": "Program", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":6,"end":19,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":19,"index":19}}, + "id": { + "type": "ArrayPattern", + "start":6,"end":14,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":14,"index":14}}, + "elements": [ + { + "type": "RestElement", + "start":7,"end":13,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":13,"index":13}}, + "argument": { + "type": "Identifier", + "start":10,"end":13,"loc":{"start":{"line":1,"column":10,"index":10},"end":{"line":1,"column":13,"index":13},"identifierName":"let"}, + "name": "let" + } + } + ] + }, + "init": { + "type": "ArrayExpression", + "start":17,"end":19,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":19,"index":19}}, + "elements": [] + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-7/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-7/input.js new file mode 100644 index 000000000000..a36c3ad65c40 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-7/input.js @@ -0,0 +1 @@ +let { let = 10 } = {}; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-7/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-7/output.json new file mode 100644 index 000000000000..9bc335e6439c --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-7/output.json @@ -0,0 +1,71 @@ +{ + "type": "File", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "errors": [ + "SyntaxError: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. (1:6)" + ], + "program": { + "type": "Program", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":22,"index":22}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":21,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":21,"index":21}}, + "id": { + "type": "ObjectPattern", + "start":4,"end":16,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":16,"index":16}}, + "properties": [ + { + "type": "ObjectProperty", + "start":6,"end":14,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":14,"index":14}}, + "key": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":9,"index":9},"identifierName":"let"}, + "name": "let" + }, + "computed": false, + "method": false, + "shorthand": true, + "value": { + "type": "AssignmentPattern", + "start":6,"end":14,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":14,"index":14}}, + "left": { + "type": "Identifier", + "start":6,"end":9,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":9,"index":9},"identifierName":"let"}, + "name": "let" + }, + "right": { + "type": "NumericLiteral", + "start":12,"end":14,"loc":{"start":{"line":1,"column":12,"index":12},"end":{"line":1,"column":14,"index":14}}, + "extra": { + "rawValue": 10, + "raw": "10" + }, + "value": 10 + } + }, + "extra": { + "shorthand": true + } + } + ] + }, + "init": { + "type": "ObjectExpression", + "start":19,"end":21,"loc":{"start":{"line":1,"column":19,"index":19},"end":{"line":1,"column":21,"index":21}}, + "properties": [] + } + } + ], + "kind": "let" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-8/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-8/input.js new file mode 100644 index 000000000000..e7332f39e363 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-8/input.js @@ -0,0 +1 @@ +const { let = 10 } = {}; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-8/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-8/output.json new file mode 100644 index 000000000000..59221a2026d8 --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-8/output.json @@ -0,0 +1,71 @@ +{ + "type": "File", + "start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}}, + "errors": [ + "SyntaxError: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. (1:8)" + ], + "program": { + "type": "Program", + "start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":24,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":24,"index":24}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":6,"end":23,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":23,"index":23}}, + "id": { + "type": "ObjectPattern", + "start":6,"end":18,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":18,"index":18}}, + "properties": [ + { + "type": "ObjectProperty", + "start":8,"end":16,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":16,"index":16}}, + "key": { + "type": "Identifier", + "start":8,"end":11,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":11,"index":11},"identifierName":"let"}, + "name": "let" + }, + "computed": false, + "method": false, + "shorthand": true, + "value": { + "type": "AssignmentPattern", + "start":8,"end":16,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":16,"index":16}}, + "left": { + "type": "Identifier", + "start":8,"end":11,"loc":{"start":{"line":1,"column":8,"index":8},"end":{"line":1,"column":11,"index":11},"identifierName":"let"}, + "name": "let" + }, + "right": { + "type": "NumericLiteral", + "start":14,"end":16,"loc":{"start":{"line":1,"column":14,"index":14},"end":{"line":1,"column":16,"index":16}}, + "extra": { + "rawValue": 10, + "raw": "10" + }, + "value": 10 + } + }, + "extra": { + "shorthand": true + } + } + ] + }, + "init": { + "type": "ObjectExpression", + "start":21,"end":23,"loc":{"start":{"line":1,"column":21,"index":21},"end":{"line":1,"column":23,"index":23}}, + "properties": [] + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-9/input.js b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-9/input.js new file mode 100644 index 000000000000..35d09470596a --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-9/input.js @@ -0,0 +1 @@ +let [let = 10] = []; diff --git a/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-9/output.json b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-9/output.json new file mode 100644 index 000000000000..0054c4e8f4ff --- /dev/null +++ b/packages/babel-parser/test/fixtures/es2015/let/let-at-binding-list-fail-9/output.json @@ -0,0 +1,56 @@ +{ + "type": "File", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}}, + "errors": [ + "SyntaxError: 'let' is not allowed to be used as a name in 'let' or 'const' declarations. (1:5)" + ], + "program": { + "type": "Program", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":20,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":20,"index":20}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":4,"end":19,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":19,"index":19}}, + "id": { + "type": "ArrayPattern", + "start":4,"end":14,"loc":{"start":{"line":1,"column":4,"index":4},"end":{"line":1,"column":14,"index":14}}, + "elements": [ + { + "type": "AssignmentPattern", + "start":5,"end":13,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":13,"index":13}}, + "left": { + "type": "Identifier", + "start":5,"end":8,"loc":{"start":{"line":1,"column":5,"index":5},"end":{"line":1,"column":8,"index":8},"identifierName":"let"}, + "name": "let" + }, + "right": { + "type": "NumericLiteral", + "start":11,"end":13,"loc":{"start":{"line":1,"column":11,"index":11},"end":{"line":1,"column":13,"index":13}}, + "extra": { + "rawValue": 10, + "raw": "10" + }, + "value": 10 + } + } + ] + }, + "init": { + "type": "ArrayExpression", + "start":17,"end":19,"loc":{"start":{"line":1,"column":17,"index":17},"end":{"line":1,"column":19,"index":19}}, + "elements": [] + } + } + ], + "kind": "let" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/324/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/324/output.json index a6fddf3bd39e..f3a75a06bb17 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/324/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/324/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, "errors": [ - "SyntaxError: 'Complex binding patterns' require an initialization value. (1:7)" + "SyntaxError: Missing initializer in destructuring declaration. (1:7)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/es2015/uncategorised/325/output.json b/packages/babel-parser/test/fixtures/es2015/uncategorised/325/output.json index 3fc899594e35..87074675d059 100644 --- a/packages/babel-parser/test/fixtures/es2015/uncategorised/325/output.json +++ b/packages/babel-parser/test/fixtures/es2015/uncategorised/325/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":7,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":7,"index":7}}, "errors": [ - "SyntaxError: 'Complex binding patterns' require an initialization value. (1:7)" + "SyntaxError: Missing initializer in destructuring declaration. (1:7)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-for-of/unexpected-number/output.json b/packages/babel-parser/test/fixtures/esprima/es2015-for-of/unexpected-number/output.json index 856e91085a2a..aa863576aee7 100644 --- a/packages/babel-parser/test/fixtures/esprima/es2015-for-of/unexpected-number/output.json +++ b/packages/babel-parser/test/fixtures/esprima/es2015-for-of/unexpected-number/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":18,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":18,"index":18}}, "errors": [ - "SyntaxError: 'Const declarations' require an initialization value. (1:13)", + "SyntaxError: Missing initializer in const declaration. (1:13)", "SyntaxError: Missing semicolon. (1:13)", "SyntaxError: Missing semicolon. (1:16)" ], diff --git a/packages/babel-parser/test/fixtures/esprima/es2015-lexical-declaration/invalid_complex_binding_without_init/output.json b/packages/babel-parser/test/fixtures/esprima/es2015-lexical-declaration/invalid_complex_binding_without_init/output.json index 716c25395d69..48db1d3a5d9a 100644 --- a/packages/babel-parser/test/fixtures/esprima/es2015-lexical-declaration/invalid_complex_binding_without_init/output.json +++ b/packages/babel-parser/test/fixtures/esprima/es2015-lexical-declaration/invalid_complex_binding_without_init/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, "errors": [ - "SyntaxError: 'Complex binding patterns' require an initialization value. (1:6)" + "SyntaxError: Missing initializer in destructuring declaration. (1:6)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0138/output.json b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0138/output.json index 21116a466270..e9f35fd6a4b7 100644 --- a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0138/output.json +++ b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0138/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, "errors": [ - "SyntaxError: 'Const declarations' require an initialization value. (1:15)" + "SyntaxError: Missing initializer in const declaration. (1:15)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0139/output.json b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0139/output.json index 060d631ab175..8365a2df197b 100644 --- a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0139/output.json +++ b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0139/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, "errors": [ - "SyntaxError: 'Const declarations' require an initialization value. (1:7)" + "SyntaxError: Missing initializer in const declaration. (1:7)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0140/output.json b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0140/output.json index a257682ebe11..1af9735f2f2c 100644 --- a/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0140/output.json +++ b/packages/babel-parser/test/fixtures/esprima/invalid-syntax/migrated_0140/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":8,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":8,"index":8}}, "errors": [ - "SyntaxError: 'Const declarations' require an initialization value. (1:7)" + "SyntaxError: Missing initializer in const declaration. (1:7)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/esprima/statement-variable/complex-pattern-requires-init/output.json b/packages/babel-parser/test/fixtures/esprima/statement-variable/complex-pattern-requires-init/output.json index 869bacc7dfd9..84c6b1979b7b 100644 --- a/packages/babel-parser/test/fixtures/esprima/statement-variable/complex-pattern-requires-init/output.json +++ b/packages/babel-parser/test/fixtures/esprima/statement-variable/complex-pattern-requires-init/output.json @@ -2,7 +2,7 @@ "type": "File", "start":0,"end":6,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":6,"index":6}}, "errors": [ - "SyntaxError: 'Complex binding patterns' require an initialization value. (1:6)" + "SyntaxError: Missing initializer in destructuring declaration. (1:6)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/output.json b/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/output.json index 27f637009e37..55b9929fc45c 100644 --- a/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/output.json +++ b/packages/babel-parser/test/fixtures/typescript/cast/unparenthesized-assert-and-assign/output.json @@ -3,7 +3,8 @@ "start":0,"end":46,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":29,"index":46}}, "errors": [ "SyntaxError: Invalid left-hand side in assignment expression. (1:0)", - "SyntaxError: Invalid left-hand side in assignment expression. (2:6)" + "SyntaxError: Invalid left-hand side in assignment expression. (2:6)", + "SyntaxError: Invalid left-hand side in object destructuring pattern. (2:6)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/typescript/class/modifier-name-parameters/output.json b/packages/babel-parser/test/fixtures/typescript/class/modifier-name-parameters/output.json index 67f37a900168..54c5974011ac 100644 --- a/packages/babel-parser/test/fixtures/typescript/class/modifier-name-parameters/output.json +++ b/packages/babel-parser/test/fixtures/typescript/class/modifier-name-parameters/output.json @@ -1,6 +1,11 @@ { "type": "File", "start":0,"end":58,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":58}}, + "errors": [ + "SyntaxError: Unexpected reserved word 'private'. (2:14)", + "SyntaxError: Unexpected reserved word 'public'. (2:23)", + "SyntaxError: Unexpected reserved word 'static'. (2:31)" + ], "program": { "type": "Program", "start":0,"end":58,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":4,"column":1,"index":58}}, diff --git a/packages/babel-parser/test/fixtures/typescript/const/no-initializer/output.json b/packages/babel-parser/test/fixtures/typescript/const/no-initializer/output.json index c3e5fb25610b..93a337822a56 100644 --- a/packages/babel-parser/test/fixtures/typescript/const/no-initializer/output.json +++ b/packages/babel-parser/test/fixtures/typescript/const/no-initializer/output.json @@ -1,6 +1,9 @@ { "type": "File", "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "errors": [ + "SyntaxError: Missing initializer in const declaration. (1:15)" + ], "program": { "type": "Program", "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, diff --git a/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/input.ts b/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/input.ts index f8d2e9cb8ae9..8f71b245823c 100644 --- a/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/input.ts +++ b/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/input.ts @@ -1,2 +1,2 @@ declare -const x: number, y: string; +const x: number = 10, y: string = "something"; diff --git a/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/output.json b/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/output.json index 0b639369894c..6c1f3faa717b 100644 --- a/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/output.json +++ b/packages/babel-parser/test/fixtures/typescript/declare/const-new-line/output.json @@ -1,9 +1,9 @@ { "type": "File", - "start":0,"end":35,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":27,"index":35}}, + "start":0,"end":54,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":46,"index":54}}, "program": { "type": "Program", - "start":0,"end":35,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":27,"index":35}}, + "start":0,"end":54,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":46,"index":54}}, "sourceType": "module", "interpreter": null, "body": [ @@ -18,11 +18,11 @@ }, { "type": "VariableDeclaration", - "start":8,"end":35,"loc":{"start":{"line":2,"column":0,"index":8},"end":{"line":2,"column":27,"index":35}}, + "start":8,"end":54,"loc":{"start":{"line":2,"column":0,"index":8},"end":{"line":2,"column":46,"index":54}}, "declarations": [ { "type": "VariableDeclarator", - "start":14,"end":23,"loc":{"start":{"line":2,"column":6,"index":14},"end":{"line":2,"column":15,"index":23}}, + "start":14,"end":28,"loc":{"start":{"line":2,"column":6,"index":14},"end":{"line":2,"column":20,"index":28}}, "id": { "type": "Identifier", "start":14,"end":23,"loc":{"start":{"line":2,"column":6,"index":14},"end":{"line":2,"column":15,"index":23},"identifierName":"x"}, @@ -36,25 +36,41 @@ } } }, - "init": null + "init": { + "type": "NumericLiteral", + "start":26,"end":28,"loc":{"start":{"line":2,"column":18,"index":26},"end":{"line":2,"column":20,"index":28}}, + "extra": { + "rawValue": 10, + "raw": "10" + }, + "value": 10 + } }, { "type": "VariableDeclarator", - "start":25,"end":34,"loc":{"start":{"line":2,"column":17,"index":25},"end":{"line":2,"column":26,"index":34}}, + "start":30,"end":53,"loc":{"start":{"line":2,"column":22,"index":30},"end":{"line":2,"column":45,"index":53}}, "id": { "type": "Identifier", - "start":25,"end":34,"loc":{"start":{"line":2,"column":17,"index":25},"end":{"line":2,"column":26,"index":34},"identifierName":"y"}, + "start":30,"end":39,"loc":{"start":{"line":2,"column":22,"index":30},"end":{"line":2,"column":31,"index":39},"identifierName":"y"}, "name": "y", "typeAnnotation": { "type": "TSTypeAnnotation", - "start":26,"end":34,"loc":{"start":{"line":2,"column":18,"index":26},"end":{"line":2,"column":26,"index":34}}, + "start":31,"end":39,"loc":{"start":{"line":2,"column":23,"index":31},"end":{"line":2,"column":31,"index":39}}, "typeAnnotation": { "type": "TSStringKeyword", - "start":28,"end":34,"loc":{"start":{"line":2,"column":20,"index":28},"end":{"line":2,"column":26,"index":34}} + "start":33,"end":39,"loc":{"start":{"line":2,"column":25,"index":33},"end":{"line":2,"column":31,"index":39}} } } }, - "init": null + "init": { + "type": "StringLiteral", + "start":42,"end":53,"loc":{"start":{"line":2,"column":34,"index":42},"end":{"line":2,"column":45,"index":53}}, + "extra": { + "rawValue": "something", + "raw": "\"something\"" + }, + "value": "something" + } } ], "kind": "const" diff --git a/packages/babel-parser/test/fixtures/typescript/declare/destructure-new-line/output.json b/packages/babel-parser/test/fixtures/typescript/declare/destructure-new-line/output.json index 82112c2678e4..12f46ecb3e07 100644 --- a/packages/babel-parser/test/fixtures/typescript/declare/destructure-new-line/output.json +++ b/packages/babel-parser/test/fixtures/typescript/declare/destructure-new-line/output.json @@ -1,6 +1,9 @@ { "type": "File", "start":0,"end":49,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":41,"index":49}}, + "errors": [ + "SyntaxError: Missing initializer in destructuring declaration. (2:40)" + ], "program": { "type": "Program", "start":0,"end":49,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":41,"index":49}}, diff --git a/packages/babel-parser/test/fixtures/typescript/dts/no-initializer/input.ts b/packages/babel-parser/test/fixtures/typescript/dts/no-initializer/input.ts new file mode 100644 index 000000000000..8caae258b97c --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/dts/no-initializer/input.ts @@ -0,0 +1 @@ +const x: number; diff --git a/packages/babel-parser/test/fixtures/typescript/dts/no-initializer/output.json b/packages/babel-parser/test/fixtures/typescript/dts/no-initializer/output.json new file mode 100644 index 000000000000..a9192cb1af2c --- /dev/null +++ b/packages/babel-parser/test/fixtures/typescript/dts/no-initializer/output.json @@ -0,0 +1,38 @@ +{ + "type": "File", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "program": { + "type": "Program", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "sourceType": "script", + "interpreter": null, + "body": [ + { + "type": "VariableDeclaration", + "start":0,"end":16,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":16,"index":16}}, + "declarations": [ + { + "type": "VariableDeclarator", + "start":6,"end":15,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":15,"index":15}}, + "id": { + "type": "Identifier", + "start":6,"end":15,"loc":{"start":{"line":1,"column":6,"index":6},"end":{"line":1,"column":15,"index":15},"identifierName":"x"}, + "name": "x", + "typeAnnotation": { + "type": "TSTypeAnnotation", + "start":7,"end":15,"loc":{"start":{"line":1,"column":7,"index":7},"end":{"line":1,"column":15,"index":15}}, + "typeAnnotation": { + "type": "TSNumberKeyword", + "start":9,"end":15,"loc":{"start":{"line":1,"column":9,"index":9},"end":{"line":1,"column":15,"index":15}} + } + } + }, + "init": null + } + ], + "kind": "const" + } + ], + "directives": [] + } +} diff --git a/packages/babel-parser/test/fixtures/typescript/interface/declare-new-line/output.json b/packages/babel-parser/test/fixtures/typescript/interface/declare-new-line/output.json index 42b06de8a2cf..261fac9031f6 100644 --- a/packages/babel-parser/test/fixtures/typescript/interface/declare-new-line/output.json +++ b/packages/babel-parser/test/fixtures/typescript/interface/declare-new-line/output.json @@ -2,7 +2,8 @@ "type": "File", "start":0,"end":22,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":2,"index":22}}, "errors": [ - "SyntaxError: Missing semicolon. (1:7)" + "SyntaxError: Missing semicolon. (1:7)", + "SyntaxError: Unexpected reserved word 'interface'. (1:8)" ], "program": { "type": "Program", diff --git a/packages/babel-parser/test/fixtures/typescript/interface/invalid-abstract-interface/output.json b/packages/babel-parser/test/fixtures/typescript/interface/invalid-abstract-interface/output.json index 7a5e16344620..9de6d15152d2 100644 --- a/packages/babel-parser/test/fixtures/typescript/interface/invalid-abstract-interface/output.json +++ b/packages/babel-parser/test/fixtures/typescript/interface/invalid-abstract-interface/output.json @@ -3,6 +3,7 @@ "start":0,"end":25,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":6,"index":25}}, "errors": [ "SyntaxError: Missing semicolon. (1:8)", + "SyntaxError: Unexpected reserved word 'interface'. (1:9)", "SyntaxError: Missing semicolon. (2:3)" ], "program": { diff --git a/packages/babel-parser/test/fixtures/typescript/interface/new-line-error/output.json b/packages/babel-parser/test/fixtures/typescript/interface/new-line-error/output.json index 7f0f930f1ac7..2577bd0675e7 100644 --- a/packages/babel-parser/test/fixtures/typescript/interface/new-line-error/output.json +++ b/packages/babel-parser/test/fixtures/typescript/interface/new-line-error/output.json @@ -2,6 +2,7 @@ "type": "File", "start":0,"end":14,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":4,"index":14}}, "errors": [ + "SyntaxError: Unexpected reserved word 'interface'. (1:0)", "SyntaxError: Missing semicolon. (2:1)" ], "program": { diff --git a/packages/babel-parser/test/fixtures/typescript/interface/new-line/output.json b/packages/babel-parser/test/fixtures/typescript/interface/new-line/output.json index cfb1ae21b44d..ecfc4a65ae62 100644 --- a/packages/babel-parser/test/fixtures/typescript/interface/new-line/output.json +++ b/packages/babel-parser/test/fixtures/typescript/interface/new-line/output.json @@ -1,6 +1,9 @@ { "type": "File", "start":0,"end":14,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":2,"index":14}}, + "errors": [ + "SyntaxError: Unexpected reserved word 'interface'. (1:0)" + ], "program": { "type": "Program", "start":0,"end":14,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":3,"column":2,"index":14}}, diff --git a/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/export-type-only-keyword/output.json b/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/export-type-only-keyword/output.json index 4754df21d84c..73d3206463af 100644 --- a/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/export-type-only-keyword/output.json +++ b/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/export-type-only-keyword/output.json @@ -1,6 +1,10 @@ { "type": "File", "start":0,"end":34,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":19,"index":34}}, + "errors": [ + "SyntaxError: Unexpected keyword 'if'. (1:6)", + "SyntaxError: Unexpected keyword 'if'. (2:14)" + ], "program": { "type": "Program", "start":0,"end":34,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":2,"column":19,"index":34}}, diff --git a/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-named-type-as-keyword/output.json b/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-named-type-as-keyword/output.json index a86152d9dd87..97af608a6ac2 100644 --- a/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-named-type-as-keyword/output.json +++ b/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-named-type-as-keyword/output.json @@ -1,6 +1,9 @@ { "type": "File", "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, + "errors": [ + "SyntaxError: Unexpected keyword 'if'. (1:17)" + ], "program": { "type": "Program", "start":0,"end":33,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":33,"index":33}}, diff --git a/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-type-only-as-as-keyword/output.json b/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-type-only-as-as-keyword/output.json index 5c16de83ee3d..4a3eaa15fda9 100644 --- a/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-type-only-as-as-keyword/output.json +++ b/packages/babel-parser/test/fixtures/typescript/type-only-import-export-specifiers/import-invalid-type-only-as-as-keyword/output.json @@ -1,6 +1,9 @@ { "type": "File", "start":0,"end":36,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":36,"index":36}}, + "errors": [ + "SyntaxError: Unexpected keyword 'if'. (1:20)" + ], "program": { "type": "Program", "start":0,"end":36,"loc":{"start":{"line":1,"column":0,"index":0},"end":{"line":1,"column":36,"index":36}}, diff --git a/scripts/parser-tests/typescript/allowlist.txt b/scripts/parser-tests/typescript/allowlist.txt index 0cfd7cca8167..624e022dc382 100644 --- a/scripts/parser-tests/typescript/allowlist.txt +++ b/scripts/parser-tests/typescript/allowlist.txt @@ -4,46 +4,10 @@ ParameterList13.ts ParameterList4.ts ParameterList5.ts ParameterList6.ts -abstractPropertyNegative.ts accessorBodyInTypeContext.ts accessorParameterAccessibilityModifier.ts -accessorWithoutBody1.ts -accessorWithoutBody2.ts -aliasUsageInAccessorsOfClass.ts -aliasUsageInArray.ts -aliasUsageInFunctionExpression.ts -aliasUsageInGenericFunction.ts -aliasUsageInIndexerOfClass.ts -aliasUsageInObjectLiteral.ts -aliasUsageInOrExpression.ts -aliasUsageInTypeArgumentOfExtendsClause.ts -aliasUsageInVarAssignment.ts -aliasUsedAsNameValue.ts -allowImportClausesToMergeWithTypes.ts -allowJscheckJsTypeParameterNoCrash.ts -amdDeclarationEmitNoExtraDeclare.ts -amdModuleConstEnumUsage.ts -amdModuleName2.ts -anonClassDeclarationEmitIsAnon.ts anyDeclare.ts -argumentsBindsToFunctionScopeArgumentList.ts -argumentsReferenceInConstructor4_Js.ts -argumentsReferenceInMethod4_Js.ts -argumentsReferenceInObjectLiteral_Js.ts -arrayOfExportedClass.ts asiAbstract.ts -asyncFunctionsAcrossFiles.ts -augmentExportEquals1.ts -augmentExportEquals1_1.ts -augmentExportEquals2.ts -augmentExportEquals2_1.ts -augmentExportEquals3.ts -augmentExportEquals3_1.ts -augmentExportEquals4.ts -augmentExportEquals4_1.ts -augmentExportEquals6.ts -augmentExportEquals6_1.ts -augmentExportEquals7.ts augmentedTypesClass.ts augmentedTypesClass2.ts augmentedTypesClass2a.ts @@ -54,224 +18,86 @@ augmentedTypesFunction.ts augmentedTypesInterface.ts augmentedTypesVar.ts bigintIndex.ts -binderBinaryExpressionStress.ts -binderBinaryExpressionStressJs.ts -bundledDtsLateExportRenaming.ts -cacheResolutions.ts -cachedModuleResolution1.ts -cachedModuleResolution2.ts -cachedModuleResolution3.ts -cachedModuleResolution4.ts -cachedModuleResolution5.ts -cachedModuleResolution6.ts -cachedModuleResolution7.ts -cachedModuleResolution8.ts -cachedModuleResolution9.ts -callOverloads2.ts -checkSuperCallBeforeThisAccessing9.ts classCannotExtendVar.ts classExpressionWithDecorator1.ts -classExtendsAcrossFiles.ts classExtendsMultipleBaseClasses.ts classOverloadForFunction.ts classWithEmptyTypeParameter.ts -collisionExportsRequireAndClass.ts -commonSourceDir5.ts -commonSourceDir6.ts -commonjsSafeImport.ts -conflictingTypeAnnotatedVar.ts +# TypeScript doesn't allow a parameter to be named arguments even in non-strict mode, which we don't catch. +collisionArgumentsArrowFunctions.ts +# TypeScript doesn't allow a parameter to be named arguments even in non-strict mode, which we don't catch. +collisionArgumentsFunction.ts +# TypeScript doesn't allow a parameter to be named arguments even in non-strict mode, which we don't catch. +collisionArgumentsFunctionExpressions.ts constDeclarations-invalidContexts.ts constDeclarations-scopes.ts constDeclarations-validContexts.ts -constEnumNamespaceReferenceCausesNoImport2.ts -constEnumNoEmitReexport.ts -constEnumNoPreserveDeclarationReexport.ts -constEnumPreserveEmitNamedExport1.ts -constEnumPreserveEmitNamedExport2.ts -constEnumPreserveEmitReexport.ts -contextualOverloadListFromArrayUnion.ts -convertKeywordsYes.ts -declarationEmitAmdModuleNameDirective.ts -declarationEmitCommonSourceDirectoryDoesNotContainAllFiles.ts -declarationEmitComputedNameCausesImportToBePainted.ts -declarationEmitComputedNameConstEnumAlias.ts -declarationEmitCrossFileImportTypeOfAmbientModule.ts -declarationEmitDefaultExportWithStaticAssignment.ts declarationEmitDestructuring2.ts declarationEmitDestructuringOptionalBindingParametersInOverloads.ts declarationEmitDestructuringParameterProperties.ts declarationEmitDestructuringWithOptionalBindingParameters.ts -declarationEmitExpandoPropertyPrivateName.ts -declarationEmitExportAssignedNamespaceNoTripleSlashTypesReference.ts -declarationEmitExportAssignment.ts -declarationEmitExportDeclaration.ts -declarationEmitExpressionInExtends6.ts -declarationEmitExpressionWithNonlocalPrivateUniqueSymbol.ts -declarationEmitForModuleImportingModuleAugmentationRetainsImport.ts -declarationEmitForTypesWhichNeedImportTypes.ts declarationEmitInterfaceWithNonEntityNameExpressionHeritage.ts -declarationEmitMixinPrivateProtected.ts -declarationEmitObjectAssignedDefaultExport.ts -declarationEmitPrefersPathKindBasedOnBundling.ts -declarationEmitPrefersPathKindBasedOnBundling2.ts -declarationEmitPrivatePromiseLikeInterface.ts -declarationEmitPrivateSymbolCausesVarDeclarationEmit2.ts -declarationEmitReadonlyComputedProperty.ts -declarationEmitStringEnumUsedInNonlocalSpread.ts -declarationImportTypeAliasInferredAndEmittable.ts -declarationMapsMultifile.ts -declarationMapsOutFile.ts -declarationsForInferredTypeFromOtherFile.ts -declarationsIndirectGeneratedAliasReference.ts declareModifierOnImport1.ts -decoratorMetadataRestParameterWithImportedType.ts -decoratorMetadataWithImportDeclarationNameCollision.ts -decoratorMetadataWithImportDeclarationNameCollision3.ts -decoratorMetadataWithImportDeclarationNameCollision4.ts -decoratorMetadataWithImportDeclarationNameCollision5.ts -decoratorMetadataWithImportDeclarationNameCollision7.ts -decoratorReferenceOnOtherProperty.ts decoratorsOnComputedProperties.ts decrementAndIncrementOperators.ts defaultArgsInOverloads.ts -defaultDeclarationEmitDefaultImport.ts defaultIsNotVisibleInLocalScope.ts -defaultPropsEmptyCurlyBecomesAnyForJs.ts defaultValueInFunctionTypes.ts -deleteOperator1.ts deleteOperatorInStrictMode.ts -dependencyViaImportAlias.ts -destructuredDeclarationEmit.ts -doubleUnderscoreExportStarConflict.ts duplicateIdentifierBindingElementInParameterDeclaration1.ts duplicateIdentifierBindingElementInParameterDeclaration2.ts duplicateIdentifierEnum.ts duplicateIdentifierInCatchBlock.ts -duplicateIdentifierRelatedSpans1.ts -duplicateIdentifierRelatedSpans2.ts -duplicateIdentifiersAcrossFileBoundaries.ts duplicateLabel1.ts duplicateLabel2.ts +duplicatePackage_withErrors.ts duplicateVarAndImport.ts duplicateVarAndImport2.ts -duplicateVarsAcrossFileBoundaries.ts dynamicImportTrailingComma.ts -dynamicNames.ts -elidedEmbeddedStatementsReplacedWithSemicolon.ts -emitClassMergedWithConstNamespaceNotElided.ts -emitSuperCallBeforeEmitParameterPropertyDeclaration1.ts -emitSuperCallBeforeEmitParameterPropertyDeclaration1ES6.ts -emitSuperCallBeforeEmitPropertyDeclarationAndParameterPropertyDeclaration1.ts -emitSuperCallBeforeEmitPropertyDeclarationAndParameterPropertyDeclaration1ES6.ts emptyGenericParamList.ts emptyTypeArgumentList.ts emptyTypeArgumentListWithNew.ts enumGenericTypeClash.ts +# We don't support ES3-style octal literal errors. +es3-oldStyleOctalLiteralInEnums.ts es3-oldStyleOctalLiteralTypes.ts -es3defaultAliasIsQuoted.ts -es5-asyncFunctionWithStatements.ts -es5-importHelpersAsyncFunctions.ts -es5-oldStyleOctalLiteralInEnums.ts -es5ModuleInternalNamedImports.ts -es6ExportAssignment2.ts -es6ExportAssignment3.ts -es6ImportDefaultBindingFollowedWithNamedImport.ts -es6ImportDefaultBindingFollowedWithNamedImport1.ts es6ImportDefaultBindingFollowedWithNamedImport1InEs5.ts -es6ImportDefaultBindingFollowedWithNamedImportDts.ts -es6ImportDefaultBindingFollowedWithNamedImportDts1.ts -es6ImportDefaultBindingFollowedWithNamedImportInEs5.ts es6ImportDefaultBindingMergeErrors.ts -es6ImportEqualsDeclaration.ts -es6ImportEqualsExportModuleCommonJsError.ts -es6ImportEqualsExportModuleEs2015Error.ts es6ImportNameSpaceImportMergeErrors.ts -es6ImportNamedImport.ts -es6ImportNamedImportAmd.ts -es6ImportNamedImportDts.ts -es6ImportNamedImportInEs5.ts -es6ImportNamedImportInExportAssignment.ts es6ImportNamedImportMergeErrors.ts -es6ImportNamedImportNoExportMember.ts -es6ImportNamedImportNoNamedExports.ts -es6ImportNamedImportWithTypesAndValues.ts -es6ModuleInternalNamedImports.ts -es6ModuleInternalNamedImports2.ts -es6UseOfTopLevelRequire.ts -esModuleInterop.ts -esModuleInteropImportTSLibHasImport.ts -esModuleInteropNamedDefaultImports.ts -esModuleInteropTslibHelpers.ts expandoFunctionContextualTypesNoValue.ts -exportAssignClassAndModule.ts exportAssignmentImportMergeNoCrash.ts -exportAssignmentMembersVisibleInAugmentation.ts exportAssignmentWithDeclareAndExportModifiers.ts exportAssignmentWithDeclareModifier.ts exportAssignmentWithExportModifier.ts -exportAssignmentWithoutAllowSyntheticDefaultImportsError.ts -exportClassExtendingIntersection.ts exportClassWithoutName.ts exportDeclarationsInAmbientNamespaces.ts -exportDefaultAbstractClass.ts +exportDeclarationsInAmbientNamespaces2.ts exportDefaultAsyncFunction2.ts -exportDefaultMarksIdentifierAsUsed.ts -exportDefaultStripsFreshness.ts exportEqualsOfModule.ts -exportImport.ts -exportImportNonInstantiatedModule2.ts exportSameNameFuncVar.ts exportSpecifierAndExportedMemberDeclaration.ts exportSpecifierAndLocalMemberDeclaration.ts -exportStarFromEmptyModule.ts -exportStarNotElided.ts +# We handle this fine, but it doesn't consider the different files together +exportSpecifierForAGlobal.ts +# We handle this fine, but it doesn't consider the different files together +exportSpecifierReferencingOuterDeclaration2.ts expressionsForbiddenInParameterInitializers.ts -extendingClassFromAliasAndUsageInIndexer.ts extendsClauseAlreadySeen.ts extendsClauseAlreadySeen2.ts -externalModuleAssignToVar.ts fileWithNextLine2.ts funClodule.ts functionAndImportNameConflict.ts functionCall15.ts -functionDeclarationWithResolutionOfTypeNamedArguments01.ts -functionExpressionInWithBlock.ts -functionExpressionWithResolutionOfTypeNamedArguments01.ts gettersAndSettersErrors.ts giant.ts -globalThisDeclarationEmit.ts -globalThisDeclarationEmit2.ts implementClausePrecedingExtends.ts implementsClauseAlreadySeen.ts importAndVariableDeclarationConflict1.ts importAndVariableDeclarationConflict3.ts importAndVariableDeclarationConflict4.ts -importAsBaseClass.ts -importDecl.ts importDeclWithClassModifiers.ts importDeclWithDeclareModifierInAmbientContext.ts -importEqualsError45874.ts -importHelpers.ts -importHelpersAmd.ts -importHelpersES6.ts -importHelpersInAmbientContext.ts -importHelpersInIsolatedModules.ts -importHelpersInTsx.tsx -importHelpersNoHelpers.ts -importHelpersNoHelpersForPrivateFields.ts -importHelpersNoModule.ts -importHelpersOutFile.ts -importHelpersSystem.ts -importNonExportedMember10.ts -importNonExportedMember11.ts -importNonExportedMember4.ts -importNonExportedMember5.ts -importNonExportedMember6.ts -importNonExportedMember7.ts -importNonExportedMember8.ts -importNonExportedMember9.ts -importWithTrailingSlash.ts -importedEnumMemberMergedWithExportedAliasIsError.ts importedModuleClassNameClash.ts indexSignatureWithAccessibilityModifier.ts indexSignatureWithInitializer1.ts @@ -279,38 +105,15 @@ indexSignatureWithTrailingComma.ts indexTypeCheck.ts indexWithoutParamType.ts indexerSignatureWithRestParam.ts -inferrenceInfiniteLoopWithSubtyping.ts -initializedParameterBeforeNonoptionalNotOptional.ts interfaceMayNotBeExtendedWitACall.ts interfaceWithImplements1.ts -invalidReferenceSyntax1.ts -isLiteral1.ts -isLiteral2.ts -isolatedModulesImportConstEnum.ts -isolatedModulesImportConstEnumTypeOnly.ts -isolatedModulesReExportType.ts -jsEnumTagOnObjectFrozen.ts -jsExportMemberMergedWithModuleAugmentation.ts -jsFileCompilationBindDuplicateIdentifier.ts -jsFileCompilationDuplicateFunctionImplementation.ts -jsFileCompilationDuplicateFunctionImplementationFileOrderReversed.ts -jsFileCompilationExternalPackageError.ts -jsFileImportPreservedWhenUsed.ts -jsNoImplicitAnyNoCascadingReferenceErrors.ts -jsdocAccessEnumType.ts -jsdocPropertyTagInvalid.ts +# We correctly identify this error, but we can't bring it in without bringing a bunch of other tests too. +interfaceNaming1.ts jsxAttributeWithoutExpressionReact.tsx letAndVarRedeclaration.ts letAsIdentifier.ts -letAsIdentifierInStrictMode.ts letDeclarations-invalidContexts.ts letDeclarations-scopes-duplicates.ts -letDeclarations-scopes-duplicates2.ts -letDeclarations-scopes-duplicates3.ts -letDeclarations-scopes-duplicates4.ts -letDeclarations-scopes-duplicates5.ts -letDeclarations-scopes-duplicates6.ts -letDeclarations-scopes-duplicates7.ts letDeclarations-scopes.ts letDeclarations-validContexts.ts letInConstDeclarations_ES5.ts @@ -319,143 +122,39 @@ letInLetConstDeclOfForOfAndForIn_ES5.ts letInLetConstDeclOfForOfAndForIn_ES6.ts letInLetDeclarations_ES5.ts letInLetDeclarations_ES6.ts -mergeWithImportedType.ts -mergedDeclarations6.ts -metadataOfClassFromAlias.ts -metadataOfClassFromAlias2.ts -metadataReferencedWithinFilteredUnion.ts mismatchedClassConstructorVariable.ts -missingSemicolonInModuleSpecifier.ts misspelledNewMetaProperty.ts modifiersOnInterfaceIndexSignature1.ts -moduleAugmentationCollidingNamesInAugmentation1.ts -moduleAugmentationDeclarationEmit1.ts -moduleAugmentationDeclarationEmit2.ts -moduleAugmentationDisallowedExtensions.ts -moduleAugmentationDuringSyntheticDefaultCheck.ts -moduleAugmentationExtendAmbientModule1.ts -moduleAugmentationExtendAmbientModule2.ts -moduleAugmentationExtendFileModule1.ts -moduleAugmentationExtendFileModule2.ts -moduleAugmentationGlobal1.ts -moduleAugmentationGlobal2.ts -moduleAugmentationGlobal3.ts -moduleAugmentationImportsAndExports1.ts -moduleAugmentationImportsAndExports2.ts -moduleAugmentationImportsAndExports3.ts -moduleAugmentationImportsAndExports4.ts -moduleAugmentationImportsAndExports5.ts -moduleAugmentationImportsAndExports6.ts -moduleAugmentationNoNewNames.ts -moduleAugmentationsBundledOutput1.ts -moduleAugmentationsImports1.ts -moduleAugmentationsImports2.ts -moduleAugmentationsImports3.ts -moduleAugmentationsImports4.ts moduleDuplicateIdentifiers.ts -moduleResolutionNoTsCJS.ts -moduleResolutionNoTsESM.ts -moduleResolutionWithSymlinks.ts -moduleResolutionWithSymlinks_withOutDir.ts -moduleResolution_automaticTypeDirectiveNames.ts moduleSharesNameWithImportDeclarationInsideIt3.ts moduleSharesNameWithImportDeclarationInsideIt5.ts -module_augmentUninstantiatedModule2.ts -multiImportExport.ts multipleClassPropertyModifiersErrors.ts multipleInheritance.ts +multipleExports.ts nameCollisions.ts -namespacesWithTypeAliasOnlyExportsMerge.ts -narrowedImports.ts -newNamesInGlobalAugmentations1.ts -noBundledEmitFromNodeModules.ts -noCrashOnImportShadowing.ts noImplicitAnyDestructuringVarDeclaration.ts -noSymbolForMergeCrash.ts -nodeModuleReexportFromDottedPath.ts -nodeResolution4.ts -nodeResolution6.ts -nodeResolution8.ts nonMergedOverloads.ts -objectLiteralMemberWithoutBlock1.ts -outModuleConcatAmd.ts -outModuleConcatCommonjs.ts -outModuleConcatCommonjsDeclarationOnly.ts -outModuleConcatES6.ts -outModuleConcatSystem.ts -outModuleConcatUmd.ts -outModuleTripleSlashRefs.ts parameterInitializerBeforeDestructuringEmit.ts parameterPropertyOutsideConstructor.ts parserConstructorDeclaration12.ts -pathMappingBasedModuleResolution3_classic.ts -pathMappingBasedModuleResolution3_node.ts -preserveUnusedImports.ts -privacyCheckExternalModuleExportAssignmentOfGenericClass.ts -privacyTopLevelAmbientExternalModuleImportWithExport.ts -privacyTopLevelAmbientExternalModuleImportWithoutExport.ts -reExportGlobalDeclaration1.ts -reExportUndefined1.ts -reExportUndefined2.ts readonlyInNonPropertyParameters.ts -recursiveExportAssignmentAndFindAliasedType1.ts -recursiveExportAssignmentAndFindAliasedType2.ts -recursiveExportAssignmentAndFindAliasedType3.ts -recursiveExportAssignmentAndFindAliasedType4.ts -recursiveExportAssignmentAndFindAliasedType5.ts -recursiveExportAssignmentAndFindAliasedType6.ts -recursiveExportAssignmentAndFindAliasedType7.ts redeclareParameterInCatchBlock.ts -reexportedMissingAlias.ts -relativeNamesInClassicResolution.ts -requireAsFunctionInExternalModule.ts -reservedWords3.ts +reExportGlobalDeclaration1.ts +# We handle this fine, but it doesn't consider the different files together +reExportGlobalDeclaration2.ts +# We handle this fine, but it doesn't consider the different files together +reExportGlobalDeclaration3.ts +# We handle this fine, but it doesn't consider the different files together +reExportGlobalDeclaration4.ts +reExportUndefined1.ts restParamModifier2.ts shadowedReservedCompilerDeclarationsWithNoEmit.ts -shorthandPropertyAssignmentInES6Module.ts sourceMap-LineBreaks.ts sourceMapValidationDecorators.ts -sourceMapValidationStatements.ts stackDepthLimitCastingType.ts staticAsIdentifier.ts staticModifierAlreadySeen.ts -strictModeReservedWord.ts strictOptionalProperties1.ts superCallFromClassThatHasNoBaseType1.ts -symbolLinkDeclarationEmitModuleNames.ts symbolMergeValueAndImportedType.ts -systemExportAssignment.ts -systemExportAssignment2.ts -systemModule11.ts -systemModule15.ts -systemModule17.ts -systemModuleWithSuperClass.ts -systemObjectShorthandRename.ts -targetEs6DecoratorMetadataImportNotElided.ts -targetTypeCastTest.ts -tsxDeepAttributeAssignabilityError.tsx -typeReferenceDirectives10.ts -typeReferenceDirectives11.ts -typeReferenceDirectives12.ts -typeReferenceDirectives13.ts -typeReferenceDirectives5.ts -typeReferenceDirectives7.ts -typeReferenceDirectives8.ts -typeReferenceDirectives9.ts -uniqueSymbolPropertyDeclarationEmit.ts -unusedImportWithSpread.ts -unusedImports1.ts -unusedImports11.ts -unusedImports12.ts -unusedImports2.ts -unusedImports3.ts -unusedImports4.ts -unusedImports5.ts -unusedInvalidTypeArguments.ts -usedImportNotElidedInJs.ts -varAndFunctionShareName.ts varArgConstructorMemberParameter.ts -withStatement.ts -withStatementErrors.ts -withStatementInternalComments.ts -withStatementNestedScope.ts diff --git a/scripts/parser-tests/typescript/error-codes.js b/scripts/parser-tests/typescript/error-codes.js index 8274005ce313..42631c593601 100644 --- a/scripts/parser-tests/typescript/error-codes.js +++ b/scripts/parser-tests/typescript/error-codes.js @@ -9,7 +9,9 @@ The commented out diagnostic codes will introduce false positive cases that shou */ export default [ - // "TS1005", // '{0}' expected. + "TS1002", // Unterminated string literal. + "TS1003", // Identifier expected. + "TS1005", // '{0}' expected. "TS1009", // Trailing comma not allowed. "TS1014", // A rest parameter must be last in a parameter list. "TS1019", // An index signature parameter cannot have a question mark. @@ -17,6 +19,8 @@ export default [ "TS1029", // '{0}' modifier must precede '{1}' modifier. "TS1030", // '{0}' modifier already seen. "TS1031", // '{0}' modifier cannot appear on a class element. + "TS1034", // 'super' must be followed by an argument list or member access. + "TS1039", // Initializers are not allowed in ambient contexts. "TS1042", // '{0}' modifier cannot be used here. "TS1048", // A rest parameter cannot have an initializer. "TS1053", // A 'set' accessor cannot have rest parameter. @@ -30,42 +34,68 @@ export default [ "TS1105", // A 'break' statement can only be used within an enclosing iteration or switch statement. "TS1107", // Jump target cannot cross function boundary. "TS1108", // A 'return' statement can only be used within a function body. + "TS1109", // Expression expected. + "TS1110", // Type expected. + "TS1011", // An element access expression should take an argument. "TS1113", // A 'default' clause cannot appear more than once in a 'switch' statement. "TS1115", // A 'continue' statement can only jump to a label of an enclosing iteration statement. "TS1116", // A 'break' statement can only jump to a label of an enclosing statement. "TS1123", // Variable declaration list cannot be empty. + "TS1126", // Unexpected end of text. + "TS1127", // Invalid character. + "TS1128", // Declaration or statement expected. + "TS1135", // Argument expression expected. + "TS1136", // Property assignment expected. "TS1141", // String literal expected. "TS1142", // Line break not permitted here. + "TS1144", // '{' or ';' expected. + "TS1155", // 'const' declarations must be initialized. + "TS1160", // Unterminated template literal. + "TS1161", // Unterminated regular expression literal. "TS1162", // An object member cannot be declared optional. "TS1163", // A 'yield' expression is only allowed in a generator body. "TS1184", // Modifiers cannot appear here. + "TS1185", // Merge conflict marker encountered. "TS1191", // An import declaration cannot have modifiers. "TS1196", // Catch clause variable type annotation must be 'any' or 'unknown' if specified. "TS1197", // Catch clause variable cannot have an initializer. "TS1200", // Line terminator not permitted before arrow. - "TS1312", // '=' can only be used in an object literal property inside a destructuring assignment. - // "TS1212", // Identifier expected. '{0}' is a reserved word in strict mode." - // "TS1213", // Identifier expected. '{0}' is a reserved word in strict mode. Class definitions are automatically in strict mode. - // "TS1214", // Identifier expected. '{0}' is a reserved word in strict mode. Modules are automatically in strict mode. + "TS1212", // Identifier expected. '{0}' is a reserved word in strict mode." + "TS1213", // Identifier expected. '{0}' is a reserved word in strict mode. Class definitions are automatically in strict mode. + "TS1214", // Identifier expected. '{0}' is a reserved word in strict mode. Modules are automatically in strict mode. "TS1246", // An interface property cannot have an initializer. "TS1247", // A type literal property cannot have an initializer. "TS1248", // A class member cannot have the 'const' keyword. + "TS1260", // Keywords cannot contain escape characters. "TS1308", // 'await' expression is only allowed within an async function. - "TS2337", // Super calls are not permitted outside constructors or in nested functions inside constructors. + "TS1312", // '=' can only be used in an object literal property inside a destructuring assignment. + "TS1384", // A 'new' expression with type arguments must always be followed by a parenthesized argument list. + "TS1385", // Function type notation must be parenthesized when used in a union type. + "TS1386", // Constructor type notation must be parenthesized when used in a union type. + "TS1437", // Namespace must be given a name. + "TS1194", // Export declarations are not permitted in a namespace. // "TS2300", // Duplicate identifier '{0}'. + "TS2337", // Super calls are not permitted outside constructors or in nested functions inside constructors. + // "TS2340", // Only public and protected methods of the base class are accessible via the 'super' keyword. "TS2364", // The left-hand side of an assignment expression must be a variable or a property access. + // "TS2369", // A parameter property is only allowed in a constructor implementation. // "TS2371", // A parameter initializer is only allowed in a function or constructor implementation. - // "TS2393", // Duplicate function implementation. + //"TS2393", // Duplicate function implementation. "TS2396", // Duplicate identifier 'arguments'. Compiler uses 'arguments' to initialize rest parameters. // "TS2440", // Import declaration conflicts with local declaration of '{0}'. // "TS2451", // Cannot redeclare block-scoped variable '{0}'. "TS2452", // An enum member cannot have a numeric name. "TS2566", // A rest element cannot have a property name. + //"TS2580", "TS2481", // Cannot initialize outer scoped variable '{0}' in the same scope as block scoped declaration '{0}'. // "TS2567", // Enum declarations can only merge with namespace or other enum declarations. "TS2659", // 'super' is only allowed in members of object literal expressions when option 'target' is 'ES2015' or higher. "TS2660", // 'super' can only be referenced in members of derived classes or object literal expressions. + //"TS2693", // 'interface' only refers to a type, but is being used as a value here. "TS2699", // Static property '{0}' conflicts with built-in property 'Function.{0}' of constructor function '{1}'. + "TS2754", // 'super' may not use type arguments. + "TS2809", // Declaration or statement expected. This '=' follows a block of statements, so if you intended to write a destructuring assignment, you might need to wrap the the whole assignment in parentheses. + "TS2815", // 'arguments' cannot be referenced in property initializers. "TS8018", // Octal literals are not allowed in enums members initializer. // "TS17012", // '{0}' is not a valid meta-property for keyword '{1}'. Did you mean '{2}'? ]; diff --git a/scripts/parser-tests/typescript/index.js b/scripts/parser-tests/typescript/index.js index df92734bddac..c18bf4d185bb 100644 --- a/scripts/parser-tests/typescript/index.js +++ b/scripts/parser-tests/typescript/index.js @@ -1,37 +1,46 @@ import path from "path"; -import fs from "fs/promises"; +import fs from "fs"; import { fileURLToPath } from "url"; -import ts from "../../../build/typescript/lib/typescript.js"; import TestRunner from "../utils/parser-test-runner.js"; -import parsingErrorCodes from "./error-codes.js"; +import ErrorCodes from "./error-codes.js"; + +const getEncoding = path => + ({ fffe: "utf-16le", feff: "utf-16be" }[ + fs.readFileSync(path).slice(0, 2).toString("hex") + ] || "utf-8"); + +const ErrorCodeRegExp = new RegExp(ErrorCodes.join("|")); const dirname = path.dirname(fileURLToPath(import.meta.url)); -async function* loadTests(dir) { - const names = await fs.readdir(dir); +function* loadTests(dir) { + const names = fs.readdirSync(dir).map(name => [name, path.join(dir, name)]); - for (const name of names) { - const contents = await fs.readFile(path.join(dir, name), "utf8"); - yield { name, contents }; + for (const [name, filename] of names) { + const encoding = getEncoding(filename); + if (encoding === "utf-16be" || encoding === "binary") continue; + yield { + name, + contents: fs.readFileSync(filename, encoding), + }; } } -const plugins = ["typescript", "decorators-legacy", "importAssertions"]; - const TSTestsPath = path.join(dirname, "../../../build/typescript/tests"); // Check if the baseline errors contain the codes that should also be thrown from babel-parser -async function baselineContainsParserErrorCodes(testName) { +function baselineContainsParserErrorCodes(testName) { try { - const baselineErrors = await fs.readFile( - path.join( - TSTestsPath, - "baselines/reference", - testName.replace(/\.tsx?$/, ".errors.txt") - ), - "utf8" + return ErrorCodeRegExp.test( + fs.readFileSync( + path.join( + TSTestsPath, + "baselines/reference", + testName.replace(/\.tsx?$/, ".errors.txt") + ), + "utf8" + ) ); - return parsingErrorCodes.some(code => baselineErrors.includes(code)); } catch (e) { if (e.code !== "ENOENT") { throw e; @@ -40,38 +49,98 @@ async function baselineContainsParserErrorCodes(testName) { } } +const IgnoreRegExp = /@noTypesAndSymbols|ts-ignore|\n#!/; +const AlwaysStrictRegExp = /(^|\n)\/\/\s*@alwaysStrict:\s*true/; + const runner = new TestRunner({ testDir: path.join(TSTestsPath, "./cases/compiler"), allowlist: path.join(dirname, "allowlist.txt"), logInterval: 50, shouldUpdate: process.argv.includes("--update-allowlist"), - async *getTests() { - for await (const test of loadTests(this.testDir)) { - const isTSX = test.name.slice(-4) === ".tsx"; - - const ast = ts.createSourceFile( - test.name, - test.contents, - ts.ScriptTarget.Latest, - false, - isTSX ? ts.ScriptKind.TSX : ts.ScriptKind.TS - ); - - yield { - contents: test.contents, - fileName: test.name, - id: test.name, - expectedError: - ast.parseDiagnostics.length > 0 || - (await baselineContainsParserErrorCodes(test.name)), - sourceType: "module", - plugins: isTSX ? plugins.concat("jsx") : plugins, - }; + *getTests() { + for (const test of loadTests(this.testDir)) { + if (IgnoreRegExp.test(test.contents)) { + yield { id: test.name, expectedError: false, contents: "" }; + continue; + } + + const strictMode = AlwaysStrictRegExp.test(test.contents) || void 0; + const files = toFiles(strictMode, test.contents, test.name); + const expectedError = + files.length > 0 && baselineContainsParserErrorCodes(test.name); + + yield { id: test.name, expectedError, contents: files }; } }, }); +function toFiles(strictMode, contents, name) { + return splitTwoslashCodeInfoFiles(contents, "default", `${name}/`) + .map(([filename, lines]) => [ + filename.replace(/\/default$/, ""), + lines.join("\n"), + ]) + .filter( + ([sourceFilename, contents]) => + !/\.(css|js|json|md)$/.test(sourceFilename) && + contents.split("\n").some(line => !/(^\s*$)|(^\/\/[^\n]*$)/.test(line)) + ) + .map(([sourceFilename, contents]) => ({ + contents, + sourceFilename, + sourceType: "unambiguous", + strictMode, + plugins: [ + ["typescript", { dts: sourceFilename.endsWith(".d.ts") }], + "decorators-legacy", + "importAssertions", + /\.(t|j)sx$/.test(sourceFilename) && "jsx", + ].filter(plugin => !!plugin), + })); +} + +const BracketedFileRegExp = /\/\/\/\/\s*\[([^\]]+)\][^\n]*(\n|$)/; +const AtFileRegExp = /(?:^|\n)\/\/\s*@filename:\s*([^\s]*)\s*(?:\n|$)/i; + +// Modified from: https://github.com/microsoft/TypeScript-Website/blob/v2/packages/ts-twoslasher/src/index.ts +function splitTwoslashCodeInfoFiles(code, defaultFileName, root = "") { + const lines = code.split(/\r\n?|\n/g); + + let nameForFile = code.includes(`@filename: ${defaultFileName}`) + ? "global.ts" + : defaultFileName; + let currentFileContent = []; + const fileMap = []; + + for (const line of lines) { + const newFileName = BracketedFileRegExp.test(line) + ? line.match(BracketedFileRegExp)[1] + : AtFileRegExp.test(line) + ? line.match(AtFileRegExp)[1] + : false; + if (newFileName) { + fileMap.push([root + nameForFile, currentFileContent]); + nameForFile = newFileName; + currentFileContent = []; + } else { + currentFileContent.push(line); + } + } + fileMap.push([root + nameForFile, currentFileContent]); + + // Basically, strip these: + // ["index.ts", []] + // ["index.ts", [""]] + const nameContent = fileMap.filter( + n => + n[1].length > 2 || + (n[1].length === 1 && n[1][0] !== "") || + (n[1].length === 2 && n[1][0] !== "content not parsed" && n[1][0] !== "") + ); + return nameContent; +} + runner.run().catch(err => { console.error(err); process.exitCode = 1; diff --git a/scripts/parser-tests/utils/parser-test-runner.js b/scripts/parser-tests/utils/parser-test-runner.js index 79e235b48b5e..acb2bceba796 100644 --- a/scripts/parser-tests/utils/parser-test-runner.js +++ b/scripts/parser-tests/utils/parser-test-runner.js @@ -58,10 +58,11 @@ class TestRunner { } parse(test, parser) { - parser(test.contents, { - sourceType: test.sourceType, - plugins: test.plugins, - }); + const tests = typeof test.contents === "string" ? [test] : test.contents; + + for (const { contents, ...options } of tests) { + parser(contents, options); + } } async getAllowlist() { @@ -207,7 +208,12 @@ class TestRunner { badnews.push(desc); badnewsDetails.push(desc + ":"); - badnewsDetails.push(...tests.map(test => ` ${test.id || test}`)); + badnewsDetails.push( + ...tests.map( + test => + ` ${test.id || test} ${test.expectedError} ${test.actualError}` + ) + ); }); console.log(`Testing complete (${summary.count} tests).`);