diff --git a/packages/babel-parser/src/parser/location.js b/packages/babel-parser/src/parser/location.js index cd8cf50ed11a..e89c6f36ebbc 100644 --- a/packages/babel-parser/src/parser/location.js +++ b/packages/babel-parser/src/parser/location.js @@ -16,6 +16,7 @@ type ErrorContext = { 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", diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index fe47f8be39bb..75214dd3a9f7 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -22,6 +22,7 @@ import { SCOPE_OTHER, } from "../util/scopeflags"; import type { ExpressionErrors } from "../parser/util"; +import { Errors } from "../parser/location"; const reservedTypes = new Set([ "_", @@ -42,6 +43,79 @@ const reservedTypes = new Set([ "void", ]); +/* eslint sort-keys: "error" */ +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", + 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`.", + 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.", + SpreadVariance: "Spread properties cannot have variance", + 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", + //todo: replace ´ by ` + 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", + 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", +}); +/* eslint-disable sort-keys */ + function isEsModuleType(bodyElement: N.Node): boolean { return ( bodyElement.type === "DeclareExportAllDeclaration" || @@ -170,10 +244,7 @@ export default (superClass: Class): Class => moduloLoc.line !== checksLoc.line || moduloLoc.column !== checksLoc.column - 1 ) { - this.raise( - moduloPos, - "Spaces between ´%´ and ´checks´ are not allowed here.", - ); + this.raise(moduloPos, flowErrors.UnexpectedSpaceBetweenModuloChecks); } if (this.eat(tt.parenL)) { node.value = this.parseExpression(); @@ -266,10 +337,7 @@ export default (superClass: Class): Class => return this.flowParseDeclareModuleExports(node); } else { if (insideModule) { - this.raise( - this.state.lastTokStart, - "`declare module` cannot be used inside another `declare module`", - ); + this.raise(this.state.lastTokStart, flowErrors.NestedDeclareModule); } return this.flowParseDeclareModule(node); } @@ -318,14 +386,14 @@ export default (superClass: Class): Class => if (!this.isContextual("type") && !this.match(tt._typeof)) { this.raise( this.state.lastTokStart, - "Imports within a `declare module` body must always be `import type` or `import typeof`", + flowErrors.InvalidNonTypeImportInDeclareModule, ); } this.parseImport(bodyNode); } else { this.expectContextual( "declare", - "Only declares and type imports are allowed inside declare module", + flowErrors.UnsupportedStatementInDeclareModule, ); bodyNode = this.flowParseDeclare(bodyNode, true); @@ -342,23 +410,28 @@ export default (superClass: Class): Class => let kind = null; let hasModuleExport = false; - const errorMessage = - "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"; body.forEach(bodyElement => { if (isEsModuleType(bodyElement)) { if (kind === "CommonJS") { - this.raise(bodyElement.start, errorMessage); + this.raise( + bodyElement.start, + flowErrors.AmbiguousDeclareModuleKind, + ); } kind = "ES"; } else if (bodyElement.type === "DeclareModuleExports") { if (hasModuleExport) { this.raise( bodyElement.start, - "Duplicate `declare module.exports` statement", + flowErrors.DuplicateDeclareModuleExports, + ); + } + if (kind === "ES") { + this.raise( + bodyElement.start, + flowErrors.AmbiguousDeclareModuleKind, ); } - if (kind === "ES") this.raise(bodyElement.start, errorMessage); kind = "CommonJS"; hasModuleExport = true; } @@ -396,9 +469,11 @@ export default (superClass: Class): Class => ) { const label = this.state.value; const suggestion = exportSuggestions[label]; - this.unexpected( + throw this.raise( this.state.start, - `\`declare export ${label}\` is not supported. Use \`${suggestion}\` instead`, + flowErrors.UnsupportedDeclareExportKind, + label, + suggestion, ); } @@ -554,22 +629,20 @@ export default (superClass: Class): Class => checkNotUnderscore(word: string) { if (word === "_") { - this.raise( - this.state.start, - "`_` is only allowed as a type argument to call or new", - ); + this.raise(this.state.start, flowErrors.UnexpectedReservedUnderscore); } } checkReservedType(word: string, startLoc: number, declaration?: boolean) { if (!reservedTypes.has(word)) return; - if (declaration) { - this.raise(startLoc, `Cannot overwrite reserved type ${word}`); - return; - } - - this.raise(startLoc, `Unexpected reserved type ${word}`); + this.raise( + startLoc, + declaration + ? flowErrors.AssignReservedType + : flowErrors.UnexpectedReservedType, + word, + ); } flowParseRestrictedIdentifier( @@ -652,11 +725,7 @@ export default (superClass: Class): Class => node.default = this.flowParseType(); } else { if (requireDefault) { - this.raise( - nodeStart, - // eslint-disable-next-line max-len - "Type parameter declaration needs a default, since a preceding type parameter declaration has a default.", - ); + this.raise(nodeStart, flowErrors.MissingTypeParamDefault); } } @@ -991,7 +1060,7 @@ export default (superClass: Class): Class => ) { this.raise( inexactStart, - "Explicit inexact syntax must appear at the end of an inexact object", + flowErrors.UnexpectedExplicitInexactInObject, ); } } @@ -1034,35 +1103,26 @@ export default (superClass: Class): Class => if (!allowSpread) { this.raise( this.state.lastTokStart, - "Explicit inexact syntax cannot appear in class or interface definitions", + flowErrors.InexactInsideNonObject, ); } else if (!allowInexact) { - this.raise( - this.state.lastTokStart, - "Explicit inexact syntax cannot appear inside an explicit exact object type", - ); + this.raise(this.state.lastTokStart, flowErrors.InexactInsideExact); } if (variance) { - this.raise( - variance.start, - "Explicit inexact syntax cannot have variance", - ); + this.raise(variance.start, flowErrors.InexactVariance); } return null; } if (!allowSpread) { - this.raise( - this.state.lastTokStart, - "Spread operator cannot appear in class or interface definitions", - ); + this.raise(this.state.lastTokStart, flowErrors.UnexpectedSpreadType); } if (protoStart != null) { this.unexpected(protoStart); } if (variance) { - this.raise(variance.start, "Spread properties cannot have variance"); + this.raise(variance.start, flowErrors.SpreadVariance); } node.argument = this.flowParseType(); @@ -1120,17 +1180,14 @@ export default (superClass: Class): Class => property.value.params.length + (property.value.rest ? 1 : 0); if (length !== paramCount) { if (property.kind === "get") { - this.raise(start, "getter must not have any formal parameters"); + this.raise(start, Errors.BadGetterArity); } else { - this.raise(start, "setter must have exactly one formal parameter"); + this.raise(start, Errors.BadSetterArity); } } if (property.kind === "set" && property.value.rest) { - this.raise( - start, - "setter function argument must not be a rest parameter", - ); + this.raise(start, Errors.BadSetterRestParameter); } } @@ -1437,7 +1494,7 @@ export default (superClass: Class): Class => throw this.raise( this.state.start, - `Unexpected token, expected "number" or "bigint"`, + flowErrors.UnexpectedSubtractionOperand, ); } @@ -1800,10 +1857,7 @@ export default (superClass: Class): Class => // e.g. Source: a ? (b): c => (d): e => f // Result 1: a ? b : (c => ((d): e => f)) // Result 2: a ? ((b): c => d) : (e => f) - this.raise( - state.start, - "Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.", - ); + this.raise(state.start, flowErrors.AmbiguousConditionalArrow); } if (failed && valid.length === 1) { @@ -2125,10 +2179,7 @@ export default (superClass: Class): Class => (!expr.extra || !expr.extra.parenthesized) && (exprList.length > 1 || !isParenthesizedExpr) ) { - this.raise( - expr.typeAnnotation.start, - "The type cast expression is expected to be wrapped with parenthesis", - ); + this.raise(expr.typeAnnotation.start, flowErrors.TypeCastInPattern); } } @@ -2303,10 +2354,7 @@ export default (superClass: Class): Class => parseAssignableListItemTypes(param: N.Pattern): N.Pattern { if (this.eat(tt.question)) { if (param.type !== "Identifier") { - this.raise( - param.start, - "A binding pattern parameter cannot be optional in an implementation signature.", - ); + this.raise(param.start, flowErrors.OptionalBindingPattern); } ((param: any): N.Identifier).optional = true; @@ -2330,11 +2378,7 @@ export default (superClass: Class): Class => node.typeAnnotation && node.right.start < node.typeAnnotation.start ) { - this.raise( - node.typeAnnotation.start, - "Type annotations must come before default assignments, " + - "e.g. instead of `age = 25: number` use `age: number = 25`", - ); + this.raise(node.typeAnnotation.start, flowErrors.TypeBeforeInitializer); } return node; @@ -2458,8 +2502,7 @@ export default (superClass: Class): Class => if (nodeIsTypeImport && specifierIsTypeImport) { this.raise( firstIdentLoc, - "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", + flowErrors.ImportTypeShorthandOnlyInPureImport, ); } @@ -2637,7 +2680,7 @@ export default (superClass: Class): Class => /*:: invariant(typeParameters) */ throw this.raise( typeParameters.start, - "Expected an arrow function after this type parameter declaration", + flowErrors.UnexpectedTokenAfterTypeParameter, ); } @@ -2896,7 +2939,7 @@ export default (superClass: Class): Class => parseTopLevel(file: N.File, program: N.Program): N.File { const fileNode = super.parseTopLevel(file, program); if (this.state.hasFlowComment) { - this.raise(this.state.pos, "Unterminated flow-comment"); + this.raise(this.state.pos, flowErrors.UnterminatedFlowComment); } return fileNode; } @@ -2904,10 +2947,7 @@ export default (superClass: Class): Class => skipBlockComment(): void { if (this.hasPlugin("flowComments") && this.skipFlowComment()) { if (this.state.hasFlowComment) { - this.unexpected( - null, - "Cannot have a flow comment inside another flow comment", - ); + this.unexpected(null, flowErrors.NestedFlowComment); } this.hasFlowCommentCompletion(); this.state.pos += this.skipFlowComment(); @@ -2918,7 +2958,7 @@ export default (superClass: Class): Class => if (this.state.hasFlowComment) { const end = this.input.indexOf("*-/", (this.state.pos += 2)); if (end === -1) { - throw this.raise(this.state.pos - 2, "Unterminated comment"); + throw this.raise(this.state.pos - 2, Errors.UnterminatedComment); } this.state.pos = end + 3; return; @@ -2961,7 +3001,7 @@ export default (superClass: Class): Class => hasFlowCommentCompletion(): void { const end = this.input.indexOf("*/", this.state.pos); if (end === -1) { - throw this.raise(this.state.pos, "Unterminated comment"); + throw this.raise(this.state.pos, Errors.UnterminatedComment); } } @@ -2973,8 +3013,9 @@ export default (superClass: Class): Class => ): void { this.raise( pos, - `Boolean enum members need to be initialized. Use either \`${memberName} = true,\` ` + - `or \`${memberName} = false,\` in enum \`${enumName}\`.`, + flowErrors.EnumBooleanMemberNotInitialized, + memberName, + enumName, ); } @@ -2985,8 +3026,10 @@ export default (superClass: Class): Class => const suggestion = memberName[0].toUpperCase() + memberName.slice(1); this.raise( pos, - `Enum member names cannot start with lowercase 'a' through 'z'. Instead of using ` + - `\`${memberName}\`, consider using \`${suggestion}\`, in enum \`${enumName}\`.`, + flowErrors.EnumInvalidMemberName, + memberName, + suggestion, + enumName, ); } @@ -2994,22 +3037,14 @@ export default (superClass: Class): Class => pos: number, { enumName, memberName }: { enumName: string, memberName: string }, ): void { - this.raise( - pos, - `Enum member names need to be unique, but the name \`${memberName}\` has already been used ` + - `before in enum \`${enumName}\`.`, - ); + this.raise(pos, flowErrors.EnumDuplicateMemberName, memberName, enumName); } flowEnumErrorInconsistentMemberValues( pos: number, { enumName }: { enumName: string }, ): void { - this.raise( - pos, - `Enum \`${enumName}\` has inconsistent member initializers. Either use no initializers, or ` + - `consistently use literals (either booleans, numbers, or strings) for all member initializers.`, - ); + this.raise(pos, flowErrors.EnumInconsistentMemberValues, enumName); } flowEnumErrorInvalidExplicitType( @@ -3019,14 +3054,14 @@ export default (superClass: Class): Class => suppliedType, }: { enumName: string, suppliedType: null | string }, ) { - const suggestion = - `Use one of \`boolean\`, \`number\`, \`string\`, or \`symbol\` in ` + - `enum \`${enumName}\`.`; - const message = + return this.raise( + pos, suppliedType === null - ? `Supplied enum type is not valid. ${suggestion}` - : `Enum type \`${suppliedType}\` is not valid. ${suggestion}`; - return this.raise(pos, message); + ? flowErrors.EnumInvalidExplicitTypeUnknownSupplied + : flowErrors.EnumInvalidExplicitType, + enumName, + suppliedType, + ); } flowEnumErrorInvalidMemberInitializer( @@ -3038,22 +3073,16 @@ export default (superClass: Class): Class => case "boolean": case "number": case "string": - message = - `Enum \`${enumName}\` has type \`${explicitType}\`, so the initializer of ` + - `\`${memberName}\` needs to be a ${explicitType} literal.`; + message = flowErrors.EnumInvalidMemberInitializerPrimaryType; break; case "symbol": - message = - `Symbol enum members cannot be initialized. Use \`${memberName},\` in ` + - `enum \`${enumName}\`.`; + message = flowErrors.EnumInvalidMemberInitializerSymbolType; break; default: // null - message = - `The enum member initializer for \`${memberName}\` needs to be a literal (either ` + - `a boolean, number, or string) in enum \`${enumName}\`.`; + message = flowErrors.EnumInvalidMemberInitializerUnknownType; } - return this.raise(pos, message); + return this.raise(pos, message, enumName, memberName, explicitType); } flowEnumErrorNumberMemberNotInitialized( @@ -3062,7 +3091,9 @@ export default (superClass: Class): Class => ): void { this.raise( pos, - `Number enum members need to be initialized, e.g. \`${memberName} = 1\` in enum \`${enumName}\`.`, + flowErrors.EnumNumberMemberNotInitialized, + enumName, + memberName, ); } @@ -3072,8 +3103,8 @@ export default (superClass: Class): Class => ): void { this.raise( pos, - `String enum members need to consistently either all use initializers, or use no initializers, ` + - `in enum \`${enumName}\`.`, + flowErrors.EnumStringMemberInconsistentlyInitailized, + enumName, ); }