From 95ff603887d6c48601e6f79d363d7223cd5cdca5 Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sun, 4 Apr 2021 02:31:07 +0900 Subject: [PATCH] Introduce parser error codes (#13033) --- .../babel-parser/src/parser/error-codes.js | 8 + .../babel-parser/src/parser/error-message.js | 437 +++++++++--------- packages/babel-parser/src/parser/error.js | 51 +- .../babel-parser/src/parser/expression.js | 14 +- packages/babel-parser/src/parser/statement.js | 10 +- packages/babel-parser/src/parser/util.js | 28 +- .../babel-parser/src/plugins/flow/index.js | 176 +++---- .../babel-parser/src/plugins/jsx/index.js | 40 +- .../babel-parser/src/plugins/placeholders.js | 10 +- .../src/plugins/typescript/index.js | 139 +++--- packages/babel-parser/src/tokenizer/index.js | 6 +- packages/babel-parser/src/tokenizer/state.js | 4 +- packages/babel-parser/src/util/class-scope.js | 4 +- .../babel-parser/src/util/expression-scope.js | 41 +- packages/babel-parser/src/util/scope.js | 4 +- packages/babel-parser/test/error-codes.js | 21 + 16 files changed, 551 insertions(+), 442 deletions(-) create mode 100644 packages/babel-parser/src/parser/error-codes.js create mode 100644 packages/babel-parser/test/error-codes.js diff --git a/packages/babel-parser/src/parser/error-codes.js b/packages/babel-parser/src/parser/error-codes.js new file mode 100644 index 000000000000..4e89302aae11 --- /dev/null +++ b/packages/babel-parser/src/parser/error-codes.js @@ -0,0 +1,8 @@ +// @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 index 2a46b8b7be00..a12f32ae23ea 100644 --- a/packages/babel-parser/src/parser/error-message.js +++ b/packages/babel-parser/src/parser/error-message.js @@ -1,4 +1,7 @@ // @flow + +import { makeErrorTemplates, ErrorCodes } from "./error"; + /* eslint sort-keys: "error" */ /** @@ -6,215 +9,225 @@ */ // The Errors key follows https://cs.chromium.org/chromium/src/v8/src/common/message-template.h unless it does not exist -export const ErrorMessages = Object.freeze({ - 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: "getter must not have any formal parameters", - BadSetterArity: "setter must have exactly one formal parameter", - BadSetterRestParameter: - "setter 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", - 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()", - ImportMetaOutsideModule: `import.meta may appear only with 'sourceType: "module"'`, - ImportOutsideModule: `'import' and 'export' may appear only with 'sourceType: "module"'`, - InvalidBigIntLiteral: "Invalid BigIntLiteral", - InvalidCodePoint: "Code point out of bounds", - 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", - ParamDupe: "Argument name clash", - PatternHasAccessor: "Object pattern can't contain getter or setter", - PatternHasMethod: "Object pattern can't contain methods", - 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: - "Primary Topic Reference found but pipelineOperator not passed 'smart' for 'proposal' 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", - UnexpectedNumericSeparator: - "A numeric separator is only allowed between two digits", - UnexpectedPrivateField: - "Private names can only be used as the name of a class element (i.e. class C { #p = 42; #m() {} } )\n or a property of member expression (i.e. this.#p).", - 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", -}); +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: "getter must not have any formal parameters", + BadSetterArity: "setter must have exactly one formal parameter", + BadSetterRestParameter: + "setter 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", + 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()", + InvalidBigIntLiteral: "Invalid BigIntLiteral", + InvalidCodePoint: "Code point out of bounds", + 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", + ParamDupe: "Argument name clash", + PatternHasAccessor: "Object pattern can't contain getter or setter", + PatternHasMethod: "Object pattern can't contain methods", + 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: + "Primary Topic Reference found but pipelineOperator not passed 'smart' for 'proposal' 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", + UnexpectedNumericSeparator: + "A numeric separator is only allowed between two digits", + UnexpectedPrivateField: + "Private names can only be used as the name of a class element (i.e. class C { #p = 42; #m() {} } )\n or a property of member expression (i.e. this.#p).", + 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 index ead11e796bb0..7c805001e67a 100644 --- a/packages/babel-parser/src/parser/error.js +++ b/packages/babel-parser/src/parser/error.js @@ -2,6 +2,7 @@ /* eslint sort-keys: "error" */ import { getLineInfo, type Position } from "../util/location"; import CommentsParser from "./comments"; +import { type ErrorCode, ErrorCodes } from "./error-codes"; // This function is used to raise exceptions on parse errors. It // takes an offset integer (into the current `input`) to indicate @@ -14,11 +15,43 @@ type ErrorContext = { loc: Position, missingPlugin?: Array, code?: string, + reasonCode?: String, }; - export type ParsingError = SyntaxError & ErrorContext; -export { ErrorMessages as Errors } from "./error-message"; +export type ErrorTemplate = { + code: ErrorCode, + template: string, + reasonCode: string, +}; +export type ErrorTemplates = { + [key: string]: ErrorTemplate, +}; + +export function makeErrorTemplates( + messages: { + [key: string]: string, + }, + code: ErrorCode, +): ErrorTemplates { + const templates: ErrorTemplates = {}; + Object.keys(messages).forEach(reasonCode => { + templates[reasonCode] = { + code, + reasonCode, + template: messages[reasonCode], + }; + }); + return Object.freeze(templates); +} + +export { ErrorCodes }; +export { + ErrorMessages as Errors, + SourceTypeModuleErrorMessages as SourceTypeModuleErrors, +} from "./error-message"; + +export type raiseFunction = (number, ErrorTemplate, ...any) => void; export default class ParserError extends CommentsParser { // Forward-declaration: defined in tokenizer/index.js @@ -37,8 +70,12 @@ export default class ParserError extends CommentsParser { return loc; } - raise(pos: number, errorTemplate: string, ...params: any): Error | empty { - return this.raiseWithData(pos, undefined, errorTemplate, ...params); + raise( + pos: number, + { code, reasonCode, template }: ErrorTemplate, + ...params: any + ): Error | empty { + return this.raiseWithData(pos, { code, reasonCode }, template, ...params); } /** @@ -55,12 +92,12 @@ export default class ParserError extends CommentsParser { */ raiseOverwrite( pos: number, - errorTemplate: string, + { code, template }: ErrorTemplate, ...params: any ): Error | empty { const loc = this.getLocationForPosition(pos); const message = - errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) + + template.replace(/%(\d+)/g, (_, i: number) => params[i]) + ` (${loc.line}:${loc.column})`; if (this.options.errorRecovery) { const errors = this.state.errors; @@ -73,7 +110,7 @@ export default class ParserError extends CommentsParser { } } } - return this._raise({ loc, pos }, message); + return this._raise({ code, loc, pos }, message); } raiseWithData( diff --git a/packages/babel-parser/src/parser/expression.js b/packages/babel-parser/src/parser/expression.js index 9a3271a8262b..3c3a640ca5e6 100644 --- a/packages/babel-parser/src/parser/expression.js +++ b/packages/babel-parser/src/parser/expression.js @@ -54,7 +54,7 @@ import { newAsyncArrowScope, newExpressionScope, } from "../util/expression-scope"; -import { Errors } from "./error"; +import { Errors, SourceTypeModuleErrors } from "./error"; /*:: import type { SourceType } from "../options"; @@ -1358,11 +1358,7 @@ export default class ExpressionParser extends LValParser { if (this.isContextual("meta")) { if (!this.inModule) { - this.raiseWithData( - id.start, - { code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED" }, - Errors.ImportMetaOutsideModule, - ); + this.raise(id.start, SourceTypeModuleErrors.ImportMetaOutsideModule); } this.sawUnambiguousESM = true; } @@ -1524,14 +1520,14 @@ export default class ExpressionParser extends LValParser { const metaProp = this.parseMetaProperty(node, meta, "target"); if (!this.scope.inNonArrowFunction && !this.scope.inClass) { - let error = Errors.UnexpectedNewTarget; + const errorTemplate = { ...Errors.UnexpectedNewTarget }; if (this.hasPlugin("classProperties")) { - error += " or class properties"; + errorTemplate.template += " or class properties"; } /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(metaProp.start, error); + this.raise(metaProp.start, errorTemplate); /* eslint-enable @babel/development-internal/dry-error-messages */ } diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 28813bb6de8b..0bc322207d01 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -3,7 +3,7 @@ import * as N from "../types"; import { types as tt, type TokenType } from "../tokenizer/types"; import ExpressionParser from "./expression"; -import { Errors } from "./error"; +import { Errors, SourceTypeModuleErrors } from "./error"; import { isIdentifierChar, isIdentifierStart, @@ -324,13 +324,7 @@ export default class StatementParser extends ExpressionParser { assertModuleNodeAllowed(node: N.Node): void { if (!this.options.allowImportExportEverywhere && !this.inModule) { - this.raiseWithData( - node.start, - { - code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED", - }, - Errors.ImportOutsideModule, - ); + this.raise(node.start, SourceTypeModuleErrors.ImportOutsideModule); } } diff --git a/packages/babel-parser/src/parser/util.js b/packages/babel-parser/src/parser/util.js index 53156c577f5d..6b768d6837eb 100644 --- a/packages/babel-parser/src/parser/util.js +++ b/packages/babel-parser/src/parser/util.js @@ -1,6 +1,6 @@ // @flow -import { types as tt, type TokenType } from "../tokenizer/types"; +import { types as tt, TokenType } from "../tokenizer/types"; import Tokenizer from "../tokenizer"; import State from "../tokenizer/state"; import type { Node } from "../types"; @@ -13,7 +13,7 @@ import ProductionParameterHandler, { PARAM_AWAIT, PARAM, } from "../util/production-parameter"; -import { Errors } from "./error"; +import { Errors, type ErrorTemplate, ErrorCodes } from "./error"; /*:: import type ScopeHandler from "../util/scope"; */ @@ -91,8 +91,8 @@ export default class UtilParser extends Tokenizer { // Asserts that following token is given contextual keyword. - expectContextual(name: string, message?: string): void { - if (!this.eatContextual(name)) this.unexpected(null, message); + expectContextual(name: string, template?: ErrorTemplate): void { + if (!this.eatContextual(name)) this.unexpected(null, template); } // Test whether a semicolon can be inserted at the current position. @@ -142,7 +142,11 @@ export default class UtilParser extends Tokenizer { assertNoSpace(message: string = "Unexpected space."): void { if (this.state.start > this.state.lastTokEnd) { /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(this.state.lastTokEnd, message); + this.raise(this.state.lastTokEnd, { + code: ErrorCodes.SyntaxError, + reasonCode: "UnexpectedSpace", + template: message, + }); /* eslint-enable @babel/development-internal/dry-error-messages */ } } @@ -152,10 +156,18 @@ export default class UtilParser extends Tokenizer { unexpected( pos: ?number, - messageOrType: string | TokenType = "Unexpected token", + messageOrType: ErrorTemplate | TokenType = { + code: ErrorCodes.SyntaxError, + reasonCode: "UnexpectedToken", + template: "Unexpected token", + }, ): empty { - if (typeof messageOrType !== "string") { - messageOrType = `Unexpected token, expected "${messageOrType.label}"`; + if (messageOrType instanceof TokenType) { + messageOrType = { + code: ErrorCodes.SyntaxError, + reasonCode: "UnexpectedToken", + template: `Unexpected token, expected "${messageOrType.label}"`, + }; } /* eslint-disable @babel/development-internal/dry-error-messages */ throw this.raise(pos != null ? pos : this.state.start, messageOrType); diff --git a/packages/babel-parser/src/plugins/flow/index.js b/packages/babel-parser/src/plugins/flow/index.js index 01bf25e2f351..7cb7d08d6d0e 100644 --- a/packages/babel-parser/src/plugins/flow/index.js +++ b/packages/babel-parser/src/plugins/flow/index.js @@ -25,7 +25,7 @@ import { SCOPE_OTHER, } from "../../util/scopeflags"; import type { ExpressionErrors } from "../../parser/util"; -import { Errors } from "../../parser/error"; +import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error"; const reservedTypes = new Set([ "_", @@ -48,91 +48,95 @@ 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 = Object.freeze({ - 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", - OptionalBindingPattern: - "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", -}); +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", + OptionalBindingPattern: + "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, +); /* eslint-disable sort-keys */ function isEsModuleType(bodyElement: N.Node): boolean { diff --git a/packages/babel-parser/src/plugins/jsx/index.js b/packages/babel-parser/src/plugins/jsx/index.js index f8b6a3dd888b..1ee94e4bd802 100644 --- a/packages/babel-parser/src/plugins/jsx/index.js +++ b/packages/babel-parser/src/plugins/jsx/index.js @@ -14,25 +14,28 @@ import * as N from "../../types"; import { isIdentifierChar, isIdentifierStart } from "../../util/identifier"; import type { Position } from "../../util/location"; import { isNewLine } from "../../util/whitespace"; -import { Errors } from "../../parser/error"; +import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error"; const HEX_NUMBER = /^[\da-fA-F]+$/; const DECIMAL_NUMBER = /^\d+$/; /* eslint sort-keys: "error" */ -const JsxErrors = Object.freeze({ - 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 <>...?", -}); +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, +); /* eslint-disable sort-keys */ // Be aware that this file is always executed and not only when the plugin is enabled. @@ -133,10 +136,11 @@ export default (superClass: Class): Class => const htmlEntity = ch === charCodes.rightCurlyBrace ? "}" : ">"; const char = this.input[this.state.pos]; - this.raise( - this.state.pos, - `Unexpected token \`${char}\`. Did you mean \`${htmlEntity}\` or \`{'${char}'}\`?`, - ); + this.raise(this.state.pos, { + code: ErrorCodes.SyntaxError, + reasonCode: "UnexpectedToken", + template: `Unexpected token \`${char}\`. Did you mean \`${htmlEntity}\` or \`{'${char}'}\`?`, + }); } /* falls through */ diff --git a/packages/babel-parser/src/plugins/placeholders.js b/packages/babel-parser/src/plugins/placeholders.js index 3364a74ef15c..82f2acf974b7 100644 --- a/packages/babel-parser/src/plugins/placeholders.js +++ b/packages/babel-parser/src/plugins/placeholders.js @@ -5,6 +5,7 @@ import * as charCodes from "charcodes"; import { types as tt, TokenType } from "../tokenizer/types"; import type Parser from "../parser"; import * as N from "../types"; +import { makeErrorTemplates, ErrorCodes } from "../parser/error"; tt.placeholder = new TokenType("%%", { startsExpr: true }); @@ -47,6 +48,13 @@ type NodeOf = $Switch< // the substituted nodes. type MaybePlaceholder = NodeOf; // | Placeholder +const PlaceHolderErrors = makeErrorTemplates( + { + ClassNameIsRequired: "A class name is required", + }, + /* code */ ErrorCodes.SyntaxError, +); + export default (superClass: Class): Class => class extends superClass { parsePlaceholder( @@ -240,7 +248,7 @@ export default (superClass: Class): Class => node.body = this.finishPlaceholder(placeholder, "ClassBody"); return this.finishNode(node, type); } else { - this.unexpected(null, "A class name is required"); + this.unexpected(null, PlaceHolderErrors.ClassNameIsRequired); } } else { this.parseClassId(node, isStatement, optionalId); diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 737b174e7367..554c55f4ec16 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -29,7 +29,12 @@ import TypeScriptScopeHandler from "./scope"; import * as charCodes from "charcodes"; import type { ExpressionErrors } from "../../parser/util"; import { PARAM } from "../../util/production-parameter"; -import { Errors } from "../../parser/error"; +import { + Errors, + makeErrorTemplates, + type ErrorTemplate, + ErrorCodes, +} from "../../parser/error"; type TsModifier = | "readonly" @@ -60,66 +65,74 @@ type ParsingContext = | "TypeParametersOrArguments"; /* eslint sort-keys: "error" */ -const TSErrors = Object.freeze({ - AbstractMethodHasImplementation: - "Method '%0' cannot have an implementation because it is marked abstract.", - 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.", - 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'", - 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", - IndexSignatureHasStatic: "Index signatures cannot have the 'static' modifier", - InvalidModifierOnTypeMember: "'%0' modifier cannot appear on a type member.", - InvalidTupleMemberLabel: - "Tuple members must be labeled with a simple 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.", - 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.", - TypeAnnotationAfterAssign: - "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`", - 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", -}); +const TSErrors = makeErrorTemplates( + { + AbstractMethodHasImplementation: + "Method '%0' cannot have an implementation because it is marked abstract.", + 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.", + 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'", + 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", + IndexSignatureHasStatic: + "Index signatures cannot have the 'static' modifier", + InvalidModifierOnTypeMember: + "'%0' modifier cannot appear on a type member.", + InvalidTupleMemberLabel: + "Tuple members must be labeled with a simple 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.", + 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.", + 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.", + 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, +); /* eslint-disable sort-keys */ // Doesn't handle "void" or "null" because those are keywords, not identifiers. @@ -216,7 +229,7 @@ export default (superClass: Class): Class => }, allowedModifiers: TsModifier[], disallowedModifiers?: TsModifier[], - errorTemplate?: string, + errorTemplate?: ErrorTemplate, ): void { for (;;) { const startPos = this.state.start; @@ -2097,7 +2110,7 @@ export default (superClass: Class): Class => ) { this.raise( importNode.start, - "A type-only import can specify a default import or named bindings, but not both.", + TSErrors.TypeImportCannotSpecifyDefaultAndNamed, ); } diff --git a/packages/babel-parser/src/tokenizer/index.js b/packages/babel-parser/src/tokenizer/index.js index 7c119c7d1111..26dec238024f 100644 --- a/packages/babel-parser/src/tokenizer/index.js +++ b/packages/babel-parser/src/tokenizer/index.js @@ -9,7 +9,7 @@ import * as charCodes from "charcodes"; import { isIdentifierStart, isIdentifierChar } from "../util/identifier"; import { types as tt, keywords as keywordTypes, type TokenType } from "./types"; import { type TokContext, types as ct } from "./context"; -import ParserErrors, { Errors } from "../parser/error"; +import ParserErrors, { Errors, type ErrorTemplate } from "../parser/error"; import { SourceLocation } from "../util/location"; import { lineBreak, @@ -115,7 +115,7 @@ export default class Tokenizer extends ParserErrors { // parser/util.js /*:: +hasPrecedingLineBreak: () => boolean; - +unexpected: (pos?: ?number, messageOrType?: string | TokenType) => empty; + +unexpected: (pos?: ?number, messageOrType?: ErrorTemplate | TokenType) => empty; +expectPlugin: (name: string, pos?: ?number) => true; */ @@ -1321,7 +1321,7 @@ export default class Tokenizer extends ParserErrors { } } - recordStrictModeErrors(pos: number, message: string) { + recordStrictModeErrors(pos: number, message: ErrorTemplate) { if (this.state.strict && !this.state.strictErrors.has(pos)) { this.raise(pos, message); } else { diff --git a/packages/babel-parser/src/tokenizer/state.js b/packages/babel-parser/src/tokenizer/state.js index b3a45637bbe9..d2bdd9636964 100644 --- a/packages/babel-parser/src/tokenizer/state.js +++ b/packages/babel-parser/src/tokenizer/state.js @@ -6,7 +6,7 @@ import { Position } from "../util/location"; import { types as ct, type TokContext } from "./context"; import { types as tt, type TokenType } from "./types"; -import type { ParsingError } from "../parser/error"; +import type { ParsingError, ErrorTemplate } from "../parser/error"; type TopicContextState = { // When a topic binding has been currently established, @@ -147,7 +147,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(); // Names of exports store. `default` is stored as a name for both // `export default foo;` and `export { foo as default };`. diff --git a/packages/babel-parser/src/util/class-scope.js b/packages/babel-parser/src/util/class-scope.js index 359af336e1a8..e5b973f7c0c8 100644 --- a/packages/babel-parser/src/util/class-scope.js +++ b/packages/babel-parser/src/util/class-scope.js @@ -5,7 +5,7 @@ import { CLASS_ELEMENT_FLAG_STATIC, type ClassElementTypes, } from "./scopeflags"; -import { Errors } from "../parser/error"; +import { Errors, type raiseFunction } from "../parser/error"; export class ClassScope { // A list of private named declared in the current class @@ -19,8 +19,6 @@ export class ClassScope { undefinedPrivateNames: Map = new Map(); } -type raiseFunction = (number, string, ...any) => void; - export default class ClassScopeHandler { stack: Array = []; declare raise: raiseFunction; diff --git a/packages/babel-parser/src/util/expression-scope.js b/packages/babel-parser/src/util/expression-scope.js index 809995864000..5d89b8d2afb8 100644 --- a/packages/babel-parser/src/util/expression-scope.js +++ b/packages/babel-parser/src/util/expression-scope.js @@ -1,5 +1,7 @@ // @flow +import type { ErrorTemplate, raiseFunction } from "../parser/error"; + /*:: declare var invariant; */ /** * @module util/expression-scope @@ -52,8 +54,6 @@ const kExpression = 0, type ExpressionScopeType = 0 | 1 | 2 | 3; -type raiseFunction = (number, string, ...any) => void; - class ExpressionScope { type: ExpressionScopeType; @@ -74,17 +74,17 @@ class ExpressionScope { } class ArrowHeadParsingScope extends ExpressionScope { - errors: Map = new Map(); + errors: Map = new Map(); constructor(type: 1 | 2) { super(type); } - recordDeclarationError(pos: number, message: string) { - this.errors.set(pos, message); + recordDeclarationError(pos: number, template: ErrorTemplate) { + this.errors.set(pos, template); } clearDeclarationError(pos: number) { this.errors.delete(pos); } - iterateErrors(iterator: (message: string, pos: number) => void) { + iterateErrors(iterator: (template: ErrorTemplate, pos: number) => void) { this.errors.forEach(iterator); } } @@ -110,17 +110,17 @@ export default class ExpressionScopeHandler { * otherwise it will be recorded to any ancestry MaybeArrowParameterDeclaration and * MaybeAsyncArrowParameterDeclaration scope until an Expression scope is seen. * @param {number} pos Error position - * @param {string} message Error message + * @param {ErrorTemplate} template Error template * @memberof ExpressionScopeHandler */ - recordParameterInitializerError(pos: number, message: string): void { + recordParameterInitializerError(pos: number, template: ErrorTemplate): void { 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(pos, message); + scope.recordDeclarationError(pos, template); } else { /*:: invariant(scope.type == kExpression) */ // Type-Expression is the boundary where initializer error can populate to @@ -129,7 +129,7 @@ export default class ExpressionScopeHandler { scope = stack[--i]; } /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(pos, message); + this.raise(pos, template); } /** @@ -149,18 +149,21 @@ export default class ExpressionScopeHandler { * arrow scope because when we finish parsing `( [(a) = []] = [] )`, it is an unambiguous assignment * expression and can not be cast to pattern * @param {number} pos - * @param {string} message + * @param {ErrorTemplate} template * @returns {void} * @memberof ExpressionScopeHandler */ - recordParenthesizedIdentifierError(pos: number, message: string): void { + recordParenthesizedIdentifierError( + pos: number, + template: ErrorTemplate, + ): void { const { stack } = this; const scope: ExpressionScope = stack[stack.length - 1]; if (scope.isCertainlyParameterDeclaration()) { - this.raise(pos, message); + this.raise(pos, template); } else if (scope.canBeArrowParameterDeclaration()) { /*:: invariant(scope instanceof ArrowHeadParsingScope) */ - scope.recordDeclarationError(pos, message); + scope.recordDeclarationError(pos, template); } else { return; } @@ -172,17 +175,17 @@ export default class ExpressionScopeHandler { * Errors will be recorded to any ancestry MaybeAsyncArrowParameterDeclaration * scope until an Expression scope is seen. * @param {number} pos - * @param {string} message + * @param {ErrorTemplate} template * @memberof ExpressionScopeHandler */ - recordAsyncArrowParametersError(pos: number, message: string): void { + recordAsyncArrowParametersError(pos: number, template: ErrorTemplate): 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(pos, message); + scope.recordDeclarationError(pos, template); } scope = stack[--i]; } @@ -193,9 +196,9 @@ export default class ExpressionScopeHandler { const currentScope = stack[stack.length - 1]; if (!currentScope.canBeArrowParameterDeclaration()) return; /*:: invariant(currentScope instanceof ArrowHeadParsingScope) */ - currentScope.iterateErrors((message, pos) => { + currentScope.iterateErrors((template, pos) => { /* eslint-disable @babel/development-internal/dry-error-messages */ - this.raise(pos, message); + this.raise(pos, template); // iterate from parent scope let i = stack.length - 2; let scope = stack[i]; diff --git a/packages/babel-parser/src/util/scope.js b/packages/babel-parser/src/util/scope.js index bd3fc8cc37c8..03c01f57d844 100644 --- a/packages/babel-parser/src/util/scope.js +++ b/packages/babel-parser/src/util/scope.js @@ -17,7 +17,7 @@ import { type BindingTypes, } from "./scopeflags"; import * as N from "../types"; -import { Errors } from "../parser/error"; +import { Errors, type raiseFunction } from "../parser/error"; // Start an AST node, attaching a start offset. export class Scope { @@ -34,8 +34,6 @@ export class Scope { } } -type raiseFunction = (number, string, ...any) => void; - // 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 { diff --git a/packages/babel-parser/test/error-codes.js b/packages/babel-parser/test/error-codes.js new file mode 100644 index 000000000000..58bb5e411a6c --- /dev/null +++ b/packages/babel-parser/test/error-codes.js @@ -0,0 +1,21 @@ +import { parse } from "../lib"; + +describe("error codes", function () { + it("raises an error with BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED and reasonCode", function () { + const code = `import "foo"`; + const { errors } = parse(code, { + errorRecovery: true, + sourceType: "script", + }); + const error = errors[0]; + expect(error.code).toBe("BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED"); + expect(error.reasonCode).toBe("ImportOutsideModule"); + }); + it("raises an error with BABEL_PARSER_SYNTAX_ERROR and reasonCode", function () { + const code = `a b`; + const { errors } = parse(code, { errorRecovery: true }); + const error = errors[0]; + expect(error.code).toBe("BABEL_PARSER_SYNTAX_ERROR"); + expect(error.reasonCode).toBe("MissingSemicolon"); + }); +});