Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: add parser message template #11192

Merged
merged 10 commits into from Mar 3, 2020
177 changes: 50 additions & 127 deletions packages/babel-parser/src/parser/expression.js

Large diffs are not rendered by default.

223 changes: 201 additions & 22 deletions packages/babel-parser/src/parser/location.js
@@ -1,5 +1,5 @@
// @flow

/* eslint sort-keys: "error" */
import { getLineInfo, type Position } from "../util/location";
import CommentsParser from "./comments";

Expand All @@ -9,6 +9,188 @@ import CommentsParser from "./comments";
// of the error message, and then raises a `SyntaxError` with that
// message.

type ErrorContext = {
pos: number,
loc: Position,
missingPlugin?: Array<string>,
code?: string,
};

// The Errors key follows https://cs.chromium.org/chromium/src/v8/src/common/message-template.h unless it does not exist
export const Errors = Object.freeze({
ArgumentsDisallowedInInitializer:
"'arguments' is not allowed in class field initializer",
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",
AwaitExpressionFormalParameter:
"await is not allowed in async function parameters",
AwaitNotInAsyncFunction:
"Can not use keyword 'await' outside an async function",
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'",
// todo: rephrase to get/set accessor
ConstructorIsAccessor: "Constructor can't have get/set modifier",
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",
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",
ForInOfLoopInitializer:
"%0 loop variable declaration may not have an initializer",
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",
ImportCallArgumentTrailingComma:
"Trailing comma is disallowed inside import(...) arguments",
ImportCallArity: "import() requires exactly one argument",
ImportCallArityLtOne: "Dynamic imports require a parameter: import('a.js')",
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"'`,
InvalidCodePoint: "Code point out of bounds",
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",
InvalidOrUnexpectedToken: "Unexpected character '%0'",
InvalidParenthesizedAssignment: "Invalid parenthesized assignment pattern",
InvalidPrivateFieldResolution: "Private name #%0 is not defined",
InvalidPropertyBindingPattern: "Binding member expression",
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.",
MalformedRegExpFlags: "Invalid regular expression flag",
MissingClassName: "A class name is required",
MissingEqInAssignment:
"Only '=' operator can be used for specifying default value.",
MissingUnicodeEscape: "Expecting Unicode escape sequence \\uXXXX",
MixingCoalesceWithLogical:
"Nullish coalescing operator(??) requires parens when mixing with logical operators",
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.",
PrivateNameRedeclaration: "Duplicate private name #%0",
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",
StrictOctalLiteral: "Octal literal 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",
//todo: rephrase this error message as it is too subjective
TrailingDecorator: "You have trailing decorators with no method",
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 '%'",
UnexpectedTokenUnaryExponentiation:
"Illegal expression. Wrap left hand side or entire exponentiation in parentheses.",
UnsupportedBind: "Binding should be performed on object property.",
//todo: rephrase this error message as it is too subjective
UnsupportedDecoratorExport:
"You can only use decorators on an export when exporting a class",
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",
//todo: remove Stage 2 as we are likely to forget updating when it progressed
UnsupportedParameterDecorator:
"Stage 2 decorators cannot be used to decorate parameters",
UnsupportedPropertyDecorator:
"Stage 2 decorators disallow object literal property decorators",
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 is not allowed in generator parameters",
ZeroDigitNumericSeparator:
"Numeric separator can not be used after leading 0",
});

export default class LocationParser extends CommentsParser {
// Forward-declaration: defined in tokenizer/index.js
/*::
Expand All @@ -26,33 +208,30 @@ export default class LocationParser extends CommentsParser {
return loc;
}

raise(
raise(pos: number, errorTemplate: string, ...params: any): Error | empty {
return this.raiseWithData(pos, undefined, errorTemplate, ...params);
}

raiseWithData(
pos: number,
message: string,
{
missingPluginNames,
code,
}: {
missingPluginNames?: Array<string>,
data?: {
missingPlugin?: Array<string>,
code?: string,
} = {},
},
errorTemplate: string,
...params: any
): Error | empty {
const loc = this.getLocationForPosition(pos);
const message =
errorTemplate.replace(/%(\d+)/g, (_, i: number) => params[i]) +
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message now supports naive %0-style templates.

` (${loc.line}:${loc.column})`;
return this._raise(Object.assign(({ loc, pos }: Object), data), message);
}

message += ` (${loc.line}:${loc.column})`;
_raise(errorContext: ErrorContext, message: string): Error | empty {
// $FlowIgnore
const err: SyntaxError & { pos: number, loc: Position } = new SyntaxError(
message,
);
err.pos = pos;
err.loc = loc;
if (missingPluginNames) {
err.missingPlugin = missingPluginNames;
}
if (code !== undefined) {
err.code = code;
}

const err: SyntaxError & ErrorContext = new SyntaxError(message);
Object.assign(err, errorContext);
if (this.options.errorRecovery) {
if (!this.isLookahead) this.state.errors.push(err);
return err;
Expand Down
66 changes: 34 additions & 32 deletions packages/babel-parser/src/parser/lval.js
Expand Up @@ -22,6 +22,7 @@ import {
import { NodeUtils } from "./node";
import { type BindingTypes, BIND_NONE } from "../util/scopeflags";
import { ExpressionErrors } from "./util";
import { Errors } from "./location";

const unwrapParenthesizedExpression = (node: Node) => {
return node.type === "ParenthesizedExpression"
Expand Down Expand Up @@ -62,7 +63,7 @@ export default class LValParser extends NodeUtils {
parenthesized.type !== "Identifier" &&
parenthesized.type !== "MemberExpression"
) {
this.raise(node.start, "Invalid parenthesized assignment pattern");
this.raise(node.start, Errors.InvalidParenthesizedAssignment);
}
}

Expand Down Expand Up @@ -114,10 +115,7 @@ export default class LValParser extends NodeUtils {

case "AssignmentExpression":
if (node.operator !== "=") {
this.raise(
node.left.end,
"Only '=' operator can be used for specifying default value.",
);
this.raise(node.left.end, Errors.MissingEqInAssignment);
}

node.type = "AssignmentPattern";
Expand All @@ -140,8 +138,8 @@ export default class LValParser extends NodeUtils {
if (prop.type === "ObjectMethod") {
const error =
prop.kind === "get" || prop.kind === "set"
? "Object pattern can't contain getter or setter"
: "Object pattern can't contain methods";
? Errors.PatternHasAccessor
: Errors.PatternHasMethod;

this.raise(prop.key.start, error);
} else if (prop.type === "SpreadElement" && !isLast) {
Expand Down Expand Up @@ -288,10 +286,7 @@ export default class LValParser extends NodeUtils {
} else {
const decorators = [];
if (this.match(tt.at) && this.hasPlugin("decorators")) {
this.raise(
this.state.start,
"Stage 2 decorators cannot be used to decorate parameters",
);
this.raise(this.state.start, Errors.UnsupportedParameterDecorator);
}
while (this.match(tt.at)) {
decorators.push(this.parseDecorator());
Expand Down Expand Up @@ -361,9 +356,10 @@ export default class LValParser extends NodeUtils {
) {
this.raise(
expr.start,
`${bindingType === BIND_NONE ? "Assigning to" : "Binding"} '${
expr.name
}' in strict mode`,
bindingType === BIND_NONE
? Errors.StrictEvalArguments
: Errors.StrictEvalArgumentsBinding,
expr.name,
);
}

Expand All @@ -382,16 +378,13 @@ export default class LValParser extends NodeUtils {
const key = `_${expr.name}`;

if (checkClashes[key]) {
this.raise(expr.start, "Argument name clash");
this.raise(expr.start, Errors.ParamDupe);
} else {
checkClashes[key] = true;
}
}
if (disallowLetBinding && expr.name === "let") {
this.raise(
expr.start,
"'let' is not allowed to be used as a name in 'let' or 'const' declarations.",
);
this.raise(expr.start, Errors.LetInLexicalBinding);
}
if (!(bindingType & BIND_NONE)) {
this.scope.declareName(expr.name, bindingType, expr.start);
Expand All @@ -400,7 +393,7 @@ export default class LValParser extends NodeUtils {

case "MemberExpression":
if (bindingType !== BIND_NONE) {
this.raise(expr.start, "Binding member expression");
this.raise(expr.start, Errors.InvalidPropertyBindingPattern);
}
break;

Expand Down Expand Up @@ -464,15 +457,24 @@ export default class LValParser extends NodeUtils {
break;

default: {
const message =
(bindingType === BIND_NONE
? "Invalid"
: /* istanbul ignore next */ "Binding invalid") +
" left-hand side" +
(contextDescription
? " in " + contextDescription
: /* istanbul ignore next */ "expression");
this.raise(expr.start, message);
if (contextDescription) {
this.raise(
expr.start,
bindingType === BIND_NONE
? Errors.InvalidLhs
: Errors.InvalidLhsBinding,
contextDescription,
);
} else {
// todo: check if contextDescription is never empty
const message =
(bindingType === BIND_NONE
? "Invalid"
: /* istanbul ignore next */ "Binding invalid") +
" left-hand side expression";

this.raise(expr.start, message);
}
}
}
}
Expand All @@ -482,7 +484,7 @@ export default class LValParser extends NodeUtils {
node.argument.type !== "Identifier" &&
node.argument.type !== "MemberExpression"
) {
this.raise(node.argument.start, "Invalid rest operator's argument");
this.raise(node.argument.start, Errors.InvalidRestAssignmentPattern);
}
}

Expand All @@ -497,10 +499,10 @@ export default class LValParser extends NodeUtils {
}

raiseRestNotLast(pos: number) {
throw this.raise(pos, `Rest element must be last element`);
throw this.raise(pos, Errors.ElementAfterRest);
}

raiseTrailingCommaAfterRest(pos: number) {
this.raise(pos, `Unexpected trailing comma after rest element`);
this.raise(pos, Errors.RestTrailingComma);
}
}