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

Introduce parser error codes #13033

8 changes: 8 additions & 0 deletions 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<typeof ErrorCodes>;
437 changes: 225 additions & 212 deletions packages/babel-parser/src/parser/error-message.js

Large diffs are not rendered by default.

51 changes: 44 additions & 7 deletions packages/babel-parser/src/parser/error.js
Expand Up @@ -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
Expand All @@ -14,11 +15,43 @@ type ErrorContext = {
loc: Position,
missingPlugin?: Array<string>,
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
Expand All @@ -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);
}

/**
Expand All @@ -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;
Expand All @@ -73,7 +110,7 @@ export default class ParserError extends CommentsParser {
}
}
}
return this._raise({ loc, pos }, message);
return this._raise({ code, loc, pos }, message);
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be good to add params as a property.
Use case: in Prettier, Unexpected reserved word 'yield' should be suppressed, Unexpected reserved word 'for' should be rethrown. prettier/prettier#10603

Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer to split the error between UnexpectedKeyword and UnexpectedContextualKeyword if that works too, and avoid exposing too much internal details about how we build errors.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, that's a better solution for that use case.

Copy link
Member

Choose a reason for hiding this comment

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

@thorn0 I was going to implement this, but I noticed that we already have two different errors:

  • UnexpectedKeyword for things like for, if, while, ...
  • UnexpectedReservedWord for things like yield, await, enum, ...

}

raiseWithData(
Expand Down
14 changes: 5 additions & 9 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 */
}

Expand Down
10 changes: 2 additions & 8 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}

Expand Down
28 changes: 20 additions & 8 deletions 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";
Expand All @@ -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";
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 */
}
}
Expand All @@ -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);
Expand Down