Introduce parser error codes (#13033)

This commit is contained in:
Sosuke Suzuki 2021-04-04 02:31:07 +09:00 committed by Nicolò Ribaudo
parent d0fcbfccdd
commit 0ee98139a6
16 changed files with 551 additions and 442 deletions

View File

@ -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<typeof ErrorCodes>;

View File

@ -1,4 +1,7 @@
// @flow // @flow
import { makeErrorTemplates, ErrorCodes } from "./error";
/* eslint sort-keys: "error" */ /* eslint sort-keys: "error" */
/** /**
@ -6,7 +9,8 @@
*/ */
// The Errors key follows https://cs.chromium.org/chromium/src/v8/src/common/message-template.h unless it does not exist // 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({ export const ErrorMessages = makeErrorTemplates(
{
AccessorIsGenerator: "A %0ter cannot be a generator", AccessorIsGenerator: "A %0ter cannot be a generator",
ArgumentsInClass: ArgumentsInClass:
"'arguments' is only allowed in functions and class methods", "'arguments' is only allowed in functions and class methods",
@ -71,8 +75,6 @@ export const ErrorMessages = Object.freeze({
ImportCallArity: "import() requires exactly %0", ImportCallArity: "import() requires exactly %0",
ImportCallNotNewExpression: "Cannot use new with import(...)", ImportCallNotNewExpression: "Cannot use new with import(...)",
ImportCallSpreadArgument: "... is not allowed in 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", InvalidBigIntLiteral: "Invalid BigIntLiteral",
InvalidCodePoint: "Code point out of bounds", InvalidCodePoint: "Code point out of bounds",
InvalidDecimal: "Invalid decimal", InvalidDecimal: "Invalid decimal",
@ -160,7 +162,8 @@ export const ErrorMessages = Object.freeze({
StrictEvalArgumentsBinding: "Binding '%0' in strict mode", StrictEvalArgumentsBinding: "Binding '%0' in strict mode",
StrictFunction: StrictFunction:
"In strict mode code, functions can only be declared at top level or inside a block", "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'", StrictNumericEscape:
"The only valid numeric escape in strict mode is '\\0'",
StrictOctalLiteral: "Legacy octal literals are not allowed in strict mode", StrictOctalLiteral: "Legacy octal literals are not allowed in strict mode",
StrictWith: "'with' in strict mode", StrictWith: "'with' in strict mode",
SuperNotAllowed: SuperNotAllowed:
@ -217,4 +220,14 @@ export const ErrorMessages = Object.freeze({
YieldInParameter: "Yield expression is not allowed in formal parameters", YieldInParameter: "Yield expression is not allowed in formal parameters",
ZeroDigitNumericSeparator: ZeroDigitNumericSeparator:
"Numeric separator can not be used after leading 0", "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,
);

View File

@ -2,6 +2,7 @@
/* eslint sort-keys: "error" */ /* eslint sort-keys: "error" */
import { getLineInfo, type Position } from "../util/location"; import { getLineInfo, type Position } from "../util/location";
import CommentsParser from "./comments"; import CommentsParser from "./comments";
import { type ErrorCode, ErrorCodes } from "./error-codes";
// This function is used to raise exceptions on parse errors. It // This function is used to raise exceptions on parse errors. It
// takes an offset integer (into the current `input`) to indicate // takes an offset integer (into the current `input`) to indicate
@ -14,11 +15,43 @@ type ErrorContext = {
loc: Position, loc: Position,
missingPlugin?: Array<string>, missingPlugin?: Array<string>,
code?: string, code?: string,
reasonCode?: String,
}; };
export type ParsingError = SyntaxError & ErrorContext; 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 { export default class ParserError extends CommentsParser {
// Forward-declaration: defined in tokenizer/index.js // Forward-declaration: defined in tokenizer/index.js
@ -37,8 +70,12 @@ export default class ParserError extends CommentsParser {
return loc; return loc;
} }
raise(pos: number, errorTemplate: string, ...params: any): Error | empty { raise(
return this.raiseWithData(pos, undefined, errorTemplate, ...params); 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( raiseOverwrite(
pos: number, pos: number,
errorTemplate: string, { code, template }: ErrorTemplate,
...params: any ...params: any
): Error | empty { ): Error | empty {
const loc = this.getLocationForPosition(pos); const loc = this.getLocationForPosition(pos);
const message = const message =
errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) + template.replace(/%(\d+)/g, (_, i: number) => params[i]) +
` (${loc.line}:${loc.column})`; ` (${loc.line}:${loc.column})`;
if (this.options.errorRecovery) { if (this.options.errorRecovery) {
const errors = this.state.errors; 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( raiseWithData(

View File

@ -54,7 +54,7 @@ import {
newAsyncArrowScope, newAsyncArrowScope,
newExpressionScope, newExpressionScope,
} from "../util/expression-scope"; } from "../util/expression-scope";
import { Errors } from "./error"; import { Errors, SourceTypeModuleErrors } from "./error";
/*:: /*::
import type { SourceType } from "../options"; import type { SourceType } from "../options";
@ -1358,11 +1358,7 @@ export default class ExpressionParser extends LValParser {
if (this.isContextual("meta")) { if (this.isContextual("meta")) {
if (!this.inModule) { if (!this.inModule) {
this.raiseWithData( this.raise(id.start, SourceTypeModuleErrors.ImportMetaOutsideModule);
id.start,
{ code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED" },
Errors.ImportMetaOutsideModule,
);
} }
this.sawUnambiguousESM = true; this.sawUnambiguousESM = true;
} }
@ -1524,14 +1520,14 @@ export default class ExpressionParser extends LValParser {
const metaProp = this.parseMetaProperty(node, meta, "target"); const metaProp = this.parseMetaProperty(node, meta, "target");
if (!this.scope.inNonArrowFunction && !this.scope.inClass) { if (!this.scope.inNonArrowFunction && !this.scope.inClass) {
let error = Errors.UnexpectedNewTarget; const errorTemplate = { ...Errors.UnexpectedNewTarget };
if (this.hasPlugin("classProperties")) { if (this.hasPlugin("classProperties")) {
error += " or class properties"; errorTemplate.template += " or class properties";
} }
/* eslint-disable @babel/development-internal/dry-error-messages */ /* 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 */ /* eslint-enable @babel/development-internal/dry-error-messages */
} }

View File

@ -3,7 +3,7 @@
import * as N from "../types"; import * as N from "../types";
import { types as tt, type TokenType } from "../tokenizer/types"; import { types as tt, type TokenType } from "../tokenizer/types";
import ExpressionParser from "./expression"; import ExpressionParser from "./expression";
import { Errors } from "./error"; import { Errors, SourceTypeModuleErrors } from "./error";
import { import {
isIdentifierChar, isIdentifierChar,
isIdentifierStart, isIdentifierStart,
@ -324,13 +324,7 @@ export default class StatementParser extends ExpressionParser {
assertModuleNodeAllowed(node: N.Node): void { assertModuleNodeAllowed(node: N.Node): void {
if (!this.options.allowImportExportEverywhere && !this.inModule) { if (!this.options.allowImportExportEverywhere && !this.inModule) {
this.raiseWithData( this.raise(node.start, SourceTypeModuleErrors.ImportOutsideModule);
node.start,
{
code: "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED",
},
Errors.ImportOutsideModule,
);
} }
} }

View File

@ -1,6 +1,6 @@
// @flow // @flow
import { types as tt, type TokenType } from "../tokenizer/types"; import { types as tt, TokenType } from "../tokenizer/types";
import Tokenizer from "../tokenizer"; import Tokenizer from "../tokenizer";
import State from "../tokenizer/state"; import State from "../tokenizer/state";
import type { Node } from "../types"; import type { Node } from "../types";
@ -13,7 +13,7 @@ import ProductionParameterHandler, {
PARAM_AWAIT, PARAM_AWAIT,
PARAM, PARAM,
} from "../util/production-parameter"; } from "../util/production-parameter";
import { Errors } from "./error"; import { Errors, type ErrorTemplate, ErrorCodes } from "./error";
/*:: /*::
import type ScopeHandler from "../util/scope"; import type ScopeHandler from "../util/scope";
*/ */
@ -91,8 +91,8 @@ export default class UtilParser extends Tokenizer {
// Asserts that following token is given contextual keyword. // Asserts that following token is given contextual keyword.
expectContextual(name: string, message?: string): void { expectContextual(name: string, template?: ErrorTemplate): void {
if (!this.eatContextual(name)) this.unexpected(null, message); if (!this.eatContextual(name)) this.unexpected(null, template);
} }
// Test whether a semicolon can be inserted at the current position. // 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 { assertNoSpace(message: string = "Unexpected space."): void {
if (this.state.start > this.state.lastTokEnd) { if (this.state.start > this.state.lastTokEnd) {
/* eslint-disable @babel/development-internal/dry-error-messages */ /* 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 */ /* eslint-enable @babel/development-internal/dry-error-messages */
} }
} }
@ -152,10 +156,18 @@ export default class UtilParser extends Tokenizer {
unexpected( unexpected(
pos: ?number, pos: ?number,
messageOrType: string | TokenType = "Unexpected token", messageOrType: ErrorTemplate | TokenType = {
code: ErrorCodes.SyntaxError,
reasonCode: "UnexpectedToken",
template: "Unexpected token",
},
): empty { ): empty {
if (typeof messageOrType !== "string") { if (messageOrType instanceof TokenType) {
messageOrType = `Unexpected token, expected "${messageOrType.label}"`; messageOrType = {
code: ErrorCodes.SyntaxError,
reasonCode: "UnexpectedToken",
template: `Unexpected token, expected "${messageOrType.label}"`,
};
} }
/* eslint-disable @babel/development-internal/dry-error-messages */ /* eslint-disable @babel/development-internal/dry-error-messages */
throw this.raise(pos != null ? pos : this.state.start, messageOrType); throw this.raise(pos != null ? pos : this.state.start, messageOrType);

View File

@ -25,7 +25,7 @@ import {
SCOPE_OTHER, SCOPE_OTHER,
} from "../../util/scopeflags"; } from "../../util/scopeflags";
import type { ExpressionErrors } from "../../parser/util"; import type { ExpressionErrors } from "../../parser/util";
import { Errors } from "../../parser/error"; import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error";
const reservedTypes = new Set([ const reservedTypes = new Set([
"_", "_",
@ -48,7 +48,8 @@ const reservedTypes = new Set([
/* eslint sort-keys: "error" */ /* 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 // 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({ const FlowErrors = makeErrorTemplates(
{
AmbiguousConditionalArrow: AmbiguousConditionalArrow:
"Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.", "Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.",
AmbiguousDeclareModuleKind: AmbiguousDeclareModuleKind:
@ -58,7 +59,8 @@ const FlowErrors = Object.freeze({
"The `declare` modifier can only appear on class fields.", "The `declare` modifier can only appear on class fields.",
DeclareClassFieldInitializer: DeclareClassFieldInitializer:
"Initializers are not allowed in fields with the `declare` modifier.", "Initializers are not allowed in fields with the `declare` modifier.",
DuplicateDeclareModuleExports: "Duplicate `declare module.exports` statement", DuplicateDeclareModuleExports:
"Duplicate `declare module.exports` statement",
EnumBooleanMemberNotInitialized: EnumBooleanMemberNotInitialized:
"Boolean enum members need to be initialized. Use either `%0 = true,` or `%0 = false,` in enum `%1`.", "Boolean enum members need to be initialized. Use either `%0 = true,` or `%0 = false,` in enum `%1`.",
EnumDuplicateMemberName: EnumDuplicateMemberName:
@ -132,7 +134,9 @@ const FlowErrors = Object.freeze({
UnsupportedStatementInDeclareModule: UnsupportedStatementInDeclareModule:
"Only declares and type imports are allowed inside declare module", "Only declares and type imports are allowed inside declare module",
UnterminatedFlowComment: "Unterminated flow-comment", UnterminatedFlowComment: "Unterminated flow-comment",
}); },
/* code */ ErrorCodes.SyntaxError,
);
/* eslint-disable sort-keys */ /* eslint-disable sort-keys */
function isEsModuleType(bodyElement: N.Node): boolean { function isEsModuleType(bodyElement: N.Node): boolean {

View File

@ -14,13 +14,14 @@ import * as N from "../../types";
import { isIdentifierChar, isIdentifierStart } from "../../util/identifier"; import { isIdentifierChar, isIdentifierStart } from "../../util/identifier";
import type { Position } from "../../util/location"; import type { Position } from "../../util/location";
import { isNewLine } from "../../util/whitespace"; import { isNewLine } from "../../util/whitespace";
import { Errors } from "../../parser/error"; import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error";
const HEX_NUMBER = /^[\da-fA-F]+$/; const HEX_NUMBER = /^[\da-fA-F]+$/;
const DECIMAL_NUMBER = /^\d+$/; const DECIMAL_NUMBER = /^\d+$/;
/* eslint sort-keys: "error" */ /* eslint sort-keys: "error" */
const JsxErrors = Object.freeze({ const JsxErrors = makeErrorTemplates(
{
AttributeIsEmpty: AttributeIsEmpty:
"JSX attributes must only be assigned a non-empty expression", "JSX attributes must only be assigned a non-empty expression",
MissingClosingTagElement: "Expected corresponding JSX closing tag for <%0>", MissingClosingTagElement: "Expected corresponding JSX closing tag for <%0>",
@ -32,7 +33,9 @@ const JsxErrors = Object.freeze({
UnterminatedJsxContent: "Unterminated JSX contents", UnterminatedJsxContent: "Unterminated JSX contents",
UnwrappedAdjacentJSXElements: UnwrappedAdjacentJSXElements:
"Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>?", "Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>?",
}); },
/* code */ ErrorCodes.SyntaxError,
);
/* eslint-disable sort-keys */ /* eslint-disable sort-keys */
// Be aware that this file is always executed and not only when the plugin is enabled. // Be aware that this file is always executed and not only when the plugin is enabled.
@ -133,10 +136,11 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const htmlEntity = const htmlEntity =
ch === charCodes.rightCurlyBrace ? "&rbrace;" : "&gt;"; ch === charCodes.rightCurlyBrace ? "&rbrace;" : "&gt;";
const char = this.input[this.state.pos]; const char = this.input[this.state.pos];
this.raise( this.raise(this.state.pos, {
this.state.pos, code: ErrorCodes.SyntaxError,
`Unexpected token \`${char}\`. Did you mean \`${htmlEntity}\` or \`{'${char}'}\`?`, reasonCode: "UnexpectedToken",
); template: `Unexpected token \`${char}\`. Did you mean \`${htmlEntity}\` or \`{'${char}'}\`?`,
});
} }
/* falls through */ /* falls through */

View File

@ -5,6 +5,7 @@ import * as charCodes from "charcodes";
import { types as tt, TokenType } from "../tokenizer/types"; import { types as tt, TokenType } from "../tokenizer/types";
import type Parser from "../parser"; import type Parser from "../parser";
import * as N from "../types"; import * as N from "../types";
import { makeErrorTemplates, ErrorCodes } from "../parser/error";
tt.placeholder = new TokenType("%%", { startsExpr: true }); tt.placeholder = new TokenType("%%", { startsExpr: true });
@ -47,6 +48,13 @@ type NodeOf<T: PlaceholderTypes> = $Switch<
// the substituted nodes. // the substituted nodes.
type MaybePlaceholder<T: PlaceholderTypes> = NodeOf<T>; // | Placeholder<T> type MaybePlaceholder<T: PlaceholderTypes> = NodeOf<T>; // | Placeholder<T>
const PlaceHolderErrors = makeErrorTemplates(
{
ClassNameIsRequired: "A class name is required",
},
/* code */ ErrorCodes.SyntaxError,
);
export default (superClass: Class<Parser>): Class<Parser> => export default (superClass: Class<Parser>): Class<Parser> =>
class extends superClass { class extends superClass {
parsePlaceholder<T: PlaceholderTypes>( parsePlaceholder<T: PlaceholderTypes>(
@ -240,7 +248,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
node.body = this.finishPlaceholder(placeholder, "ClassBody"); node.body = this.finishPlaceholder(placeholder, "ClassBody");
return this.finishNode(node, type); return this.finishNode(node, type);
} else { } else {
this.unexpected(null, "A class name is required"); this.unexpected(null, PlaceHolderErrors.ClassNameIsRequired);
} }
} else { } else {
this.parseClassId(node, isStatement, optionalId); this.parseClassId(node, isStatement, optionalId);

View File

@ -29,7 +29,12 @@ import TypeScriptScopeHandler from "./scope";
import * as charCodes from "charcodes"; import * as charCodes from "charcodes";
import type { ExpressionErrors } from "../../parser/util"; import type { ExpressionErrors } from "../../parser/util";
import { PARAM } from "../../util/production-parameter"; import { PARAM } from "../../util/production-parameter";
import { Errors } from "../../parser/error"; import {
Errors,
makeErrorTemplates,
type ErrorTemplate,
ErrorCodes,
} from "../../parser/error";
type TsModifier = type TsModifier =
| "readonly" | "readonly"
@ -60,7 +65,8 @@ type ParsingContext =
| "TypeParametersOrArguments"; | "TypeParametersOrArguments";
/* eslint sort-keys: "error" */ /* eslint sort-keys: "error" */
const TSErrors = Object.freeze({ const TSErrors = makeErrorTemplates(
{
AbstractMethodHasImplementation: AbstractMethodHasImplementation:
"Method '%0' cannot have an implementation because it is marked abstract.", "Method '%0' cannot have an implementation because it is marked abstract.",
ClassMethodHasDeclare: "Class methods cannot have the 'declare' modifier", ClassMethodHasDeclare: "Class methods cannot have the 'declare' modifier",
@ -86,8 +92,10 @@ const TSErrors = Object.freeze({
"Index signatures cannot have an accessibility modifier ('%0')", "Index signatures cannot have an accessibility modifier ('%0')",
IndexSignatureHasDeclare: IndexSignatureHasDeclare:
"Index signatures cannot have the 'declare' modifier", "Index signatures cannot have the 'declare' modifier",
IndexSignatureHasStatic: "Index signatures cannot have the 'static' modifier", IndexSignatureHasStatic:
InvalidModifierOnTypeMember: "'%0' modifier cannot appear on a type member.", "Index signatures cannot have the 'static' modifier",
InvalidModifierOnTypeMember:
"'%0' modifier cannot appear on a type member.",
InvalidTupleMemberLabel: InvalidTupleMemberLabel:
"Tuple members must be labeled with a simple identifier.", "Tuple members must be labeled with a simple identifier.",
MixedLabeledAndUnlabeledElements: MixedLabeledAndUnlabeledElements:
@ -108,19 +116,24 @@ const TSErrors = Object.freeze({
"'readonly' modifier can only appear on a property declaration or index signature.", "'readonly' modifier can only appear on a property declaration or index signature.",
TypeAnnotationAfterAssign: TypeAnnotationAfterAssign:
"Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`", "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: UnexpectedParameterModifier:
"A parameter property is only allowed in a constructor implementation.", "A parameter property is only allowed in a constructor implementation.",
UnexpectedReadonly: UnexpectedReadonly:
"'readonly' type modifier is only permitted on array and tuple literal types.", "'readonly' type modifier is only permitted on array and tuple literal types.",
UnexpectedTypeAnnotation: "Did not expect a type annotation here.", UnexpectedTypeAnnotation: "Did not expect a type annotation here.",
UnexpectedTypeCastInParameter: "Unexpected type cast in parameter position.", UnexpectedTypeCastInParameter:
"Unexpected type cast in parameter position.",
UnsupportedImportTypeArgument: UnsupportedImportTypeArgument:
"Argument in a type import must be a string literal", "Argument in a type import must be a string literal",
UnsupportedParameterPropertyKind: UnsupportedParameterPropertyKind:
"A parameter property may not be declared using a binding pattern.", "A parameter property may not be declared using a binding pattern.",
UnsupportedSignatureParameterKind: UnsupportedSignatureParameterKind:
"Name in a signature must be an Identifier, ObjectPattern or ArrayPattern, instead got %0", "Name in a signature must be an Identifier, ObjectPattern or ArrayPattern, instead got %0",
}); },
/* code */ ErrorCodes.SyntaxError,
);
/* eslint-disable sort-keys */ /* eslint-disable sort-keys */
// Doesn't handle "void" or "null" because those are keywords, not identifiers. // Doesn't handle "void" or "null" because those are keywords, not identifiers.
@ -217,7 +230,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
}, },
allowedModifiers: TsModifier[], allowedModifiers: TsModifier[],
disallowedModifiers?: TsModifier[], disallowedModifiers?: TsModifier[],
errorTemplate?: string, errorTemplate?: ErrorTemplate,
): void { ): void {
for (;;) { for (;;) {
const startPos = this.state.start; const startPos = this.state.start;
@ -2098,7 +2111,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
) { ) {
this.raise( this.raise(
importNode.start, importNode.start,
"A type-only import can specify a default import or named bindings, but not both.", TSErrors.TypeImportCannotSpecifyDefaultAndNamed,
); );
} }

View File

@ -9,7 +9,7 @@ import * as charCodes from "charcodes";
import { isIdentifierStart, isIdentifierChar } from "../util/identifier"; import { isIdentifierStart, isIdentifierChar } from "../util/identifier";
import { types as tt, keywords as keywordTypes, type TokenType } from "./types"; import { types as tt, keywords as keywordTypes, type TokenType } from "./types";
import { type TokContext, types as ct } from "./context"; 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 { SourceLocation } from "../util/location";
import { import {
lineBreak, lineBreak,
@ -115,7 +115,7 @@ export default class Tokenizer extends ParserErrors {
// parser/util.js // parser/util.js
/*:: /*::
+hasPrecedingLineBreak: () => boolean; +hasPrecedingLineBreak: () => boolean;
+unexpected: (pos?: ?number, messageOrType?: string | TokenType) => empty; +unexpected: (pos?: ?number, messageOrType?: ErrorTemplate | TokenType) => empty;
+expectPlugin: (name: string, pos?: ?number) => true; +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)) { if (this.state.strict && !this.state.strictErrors.has(pos)) {
this.raise(pos, message); this.raise(pos, message);
} else { } else {

View File

@ -6,7 +6,7 @@ import { Position } from "../util/location";
import { types as ct, type TokContext } from "./context"; import { types as ct, type TokContext } from "./context";
import { types as tt, type TokenType } from "./types"; import { types as tt, type TokenType } from "./types";
import type { ParsingError } from "../parser/error"; import type { ParsingError, ErrorTemplate } from "../parser/error";
type TopicContextState = { type TopicContextState = {
// When a topic binding has been currently established, // 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 // todo(JLHwung): set strictErrors to null and avoid recording string errors
// after a non-directive is parsed // after a non-directive is parsed
strictErrors: Map<number, string> = new Map(); strictErrors: Map<number, ErrorTemplate> = new Map();
// Names of exports store. `default` is stored as a name for both // Names of exports store. `default` is stored as a name for both
// `export default foo;` and `export { foo as default };`. // `export default foo;` and `export { foo as default };`.

View File

@ -5,7 +5,7 @@ import {
CLASS_ELEMENT_FLAG_STATIC, CLASS_ELEMENT_FLAG_STATIC,
type ClassElementTypes, type ClassElementTypes,
} from "./scopeflags"; } from "./scopeflags";
import { Errors } from "../parser/error"; import { Errors, type raiseFunction } from "../parser/error";
export class ClassScope { export class ClassScope {
// A list of private named declared in the current class // A list of private named declared in the current class
@ -19,8 +19,6 @@ export class ClassScope {
undefinedPrivateNames: Map<string, number> = new Map(); undefinedPrivateNames: Map<string, number> = new Map();
} }
type raiseFunction = (number, string, ...any) => void;
export default class ClassScopeHandler { export default class ClassScopeHandler {
stack: Array<ClassScope> = []; stack: Array<ClassScope> = [];
declare raise: raiseFunction; declare raise: raiseFunction;

View File

@ -1,5 +1,7 @@
// @flow // @flow
import type { ErrorTemplate, raiseFunction } from "../parser/error";
/*:: declare var invariant; */ /*:: declare var invariant; */
/** /**
* @module util/expression-scope * @module util/expression-scope
@ -52,8 +54,6 @@ const kExpression = 0,
type ExpressionScopeType = 0 | 1 | 2 | 3; type ExpressionScopeType = 0 | 1 | 2 | 3;
type raiseFunction = (number, string, ...any) => void;
class ExpressionScope { class ExpressionScope {
type: ExpressionScopeType; type: ExpressionScopeType;
@ -74,17 +74,17 @@ class ExpressionScope {
} }
class ArrowHeadParsingScope extends ExpressionScope { class ArrowHeadParsingScope extends ExpressionScope {
errors: Map</* pos */ number, /* message */ string> = new Map(); errors: Map</* pos */ number, /* message */ ErrorTemplate> = new Map();
constructor(type: 1 | 2) { constructor(type: 1 | 2) {
super(type); super(type);
} }
recordDeclarationError(pos: number, message: string) { recordDeclarationError(pos: number, template: ErrorTemplate) {
this.errors.set(pos, message); this.errors.set(pos, template);
} }
clearDeclarationError(pos: number) { clearDeclarationError(pos: number) {
this.errors.delete(pos); this.errors.delete(pos);
} }
iterateErrors(iterator: (message: string, pos: number) => void) { iterateErrors(iterator: (template: ErrorTemplate, pos: number) => void) {
this.errors.forEach(iterator); this.errors.forEach(iterator);
} }
} }
@ -110,17 +110,17 @@ export default class ExpressionScopeHandler {
* otherwise it will be recorded to any ancestry MaybeArrowParameterDeclaration and * otherwise it will be recorded to any ancestry MaybeArrowParameterDeclaration and
* MaybeAsyncArrowParameterDeclaration scope until an Expression scope is seen. * MaybeAsyncArrowParameterDeclaration scope until an Expression scope is seen.
* @param {number} pos Error position * @param {number} pos Error position
* @param {string} message Error message * @param {ErrorTemplate} template Error template
* @memberof ExpressionScopeHandler * @memberof ExpressionScopeHandler
*/ */
recordParameterInitializerError(pos: number, message: string): void { recordParameterInitializerError(pos: number, template: ErrorTemplate): void {
const { stack } = this; const { stack } = this;
let i = stack.length - 1; let i = stack.length - 1;
let scope: ExpressionScope = stack[i]; let scope: ExpressionScope = stack[i];
while (!scope.isCertainlyParameterDeclaration()) { while (!scope.isCertainlyParameterDeclaration()) {
if (scope.canBeArrowParameterDeclaration()) { if (scope.canBeArrowParameterDeclaration()) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */ /*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.recordDeclarationError(pos, message); scope.recordDeclarationError(pos, template);
} else { } else {
/*:: invariant(scope.type == kExpression) */ /*:: invariant(scope.type == kExpression) */
// Type-Expression is the boundary where initializer error can populate to // Type-Expression is the boundary where initializer error can populate to
@ -129,7 +129,7 @@ export default class ExpressionScopeHandler {
scope = stack[--i]; scope = stack[--i];
} }
/* eslint-disable @babel/development-internal/dry-error-messages */ /* 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 * arrow scope because when we finish parsing `( [(a) = []] = [] )`, it is an unambiguous assignment
* expression and can not be cast to pattern * expression and can not be cast to pattern
* @param {number} pos * @param {number} pos
* @param {string} message * @param {ErrorTemplate} template
* @returns {void} * @returns {void}
* @memberof ExpressionScopeHandler * @memberof ExpressionScopeHandler
*/ */
recordParenthesizedIdentifierError(pos: number, message: string): void { recordParenthesizedIdentifierError(
pos: number,
template: ErrorTemplate,
): void {
const { stack } = this; const { stack } = this;
const scope: ExpressionScope = stack[stack.length - 1]; const scope: ExpressionScope = stack[stack.length - 1];
if (scope.isCertainlyParameterDeclaration()) { if (scope.isCertainlyParameterDeclaration()) {
this.raise(pos, message); this.raise(pos, template);
} else if (scope.canBeArrowParameterDeclaration()) { } else if (scope.canBeArrowParameterDeclaration()) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */ /*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.recordDeclarationError(pos, message); scope.recordDeclarationError(pos, template);
} else { } else {
return; return;
} }
@ -172,17 +175,17 @@ export default class ExpressionScopeHandler {
* Errors will be recorded to any ancestry MaybeAsyncArrowParameterDeclaration * Errors will be recorded to any ancestry MaybeAsyncArrowParameterDeclaration
* scope until an Expression scope is seen. * scope until an Expression scope is seen.
* @param {number} pos * @param {number} pos
* @param {string} message * @param {ErrorTemplate} template
* @memberof ExpressionScopeHandler * @memberof ExpressionScopeHandler
*/ */
recordAsyncArrowParametersError(pos: number, message: string): void { recordAsyncArrowParametersError(pos: number, template: ErrorTemplate): void {
const { stack } = this; const { stack } = this;
let i = stack.length - 1; let i = stack.length - 1;
let scope: ExpressionScope = stack[i]; let scope: ExpressionScope = stack[i];
while (scope.canBeArrowParameterDeclaration()) { while (scope.canBeArrowParameterDeclaration()) {
if (scope.type === kMaybeAsyncArrowParameterDeclaration) { if (scope.type === kMaybeAsyncArrowParameterDeclaration) {
/*:: invariant(scope instanceof ArrowHeadParsingScope) */ /*:: invariant(scope instanceof ArrowHeadParsingScope) */
scope.recordDeclarationError(pos, message); scope.recordDeclarationError(pos, template);
} }
scope = stack[--i]; scope = stack[--i];
} }
@ -193,9 +196,9 @@ export default class ExpressionScopeHandler {
const currentScope = stack[stack.length - 1]; const currentScope = stack[stack.length - 1];
if (!currentScope.canBeArrowParameterDeclaration()) return; if (!currentScope.canBeArrowParameterDeclaration()) return;
/*:: invariant(currentScope instanceof ArrowHeadParsingScope) */ /*:: invariant(currentScope instanceof ArrowHeadParsingScope) */
currentScope.iterateErrors((message, pos) => { currentScope.iterateErrors((template, pos) => {
/* eslint-disable @babel/development-internal/dry-error-messages */ /* eslint-disable @babel/development-internal/dry-error-messages */
this.raise(pos, message); this.raise(pos, template);
// iterate from parent scope // iterate from parent scope
let i = stack.length - 2; let i = stack.length - 2;
let scope = stack[i]; let scope = stack[i];

View File

@ -17,7 +17,7 @@ import {
type BindingTypes, type BindingTypes,
} from "./scopeflags"; } from "./scopeflags";
import * as N from "../types"; 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. // Start an AST node, attaching a start offset.
export class Scope { 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 // The functions in this module keep track of declared variables in the
// current scope in order to detect duplicate variable names. // current scope in order to detect duplicate variable names.
export default class ScopeHandler<IScope: Scope = Scope> { export default class ScopeHandler<IScope: Scope = Scope> {

View File

@ -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");
});
});