Properly parse export default from when exportDefaultFrom is not enabled (#11676)

Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
Huáng Jùnliàng
2020-06-05 10:08:21 -04:00
committed by GitHub
parent 3874470841
commit 71d3527ef5
22 changed files with 279 additions and 6 deletions

View File

@@ -42,6 +42,8 @@ export const ErrorMessages = Object.freeze({
DuplicateRegExpFlags: "Duplicate regular expression flag",
ElementAfterRest: "Rest element must be last element",
EscapedCharNotAnIdentifier: "Invalid Unicode escape",
ExportDefaultFromAsIdentifier:
"'from' is not allowed as an identifier after 'export default'",
ForInOfLoopInitializer:
"%0 loop variable declaration may not have an initializer",
GeneratorInSingleStatementContext:

View File

@@ -1857,10 +1857,24 @@ export default class StatementParser extends ExpressionParser {
}
const next = this.nextTokenStart();
return (
const hasFrom = this.isUnparsedContextual(next, "from");
if (
this.input.charCodeAt(next) === charCodes.comma ||
this.isUnparsedContextual(next, "from")
);
(this.match(tt.name) && hasFrom)
) {
return true;
}
// lookahead again when `export default from` is seen
if (this.match(tt._default) && hasFrom) {
const nextAfterFrom = this.input.charCodeAt(
this.nextTokenStartSince(next + 4),
);
return (
nextAfterFrom === charCodes.quotationMark ||
nextAfterFrom === charCodes.apostrophe
);
}
return false;
}
parseExportFrom(node: N.ExportNamedDeclaration, expect?: boolean): void {
@@ -1911,6 +1925,18 @@ export default class StatementParser extends ExpressionParser {
if (isDefault) {
// Default exports
this.checkDuplicateExports(node, "default");
if (this.hasPlugin("exportDefaultFrom")) {
const declaration = ((node: any): N.ExportDefaultDeclaration)
.declaration;
if (
declaration.type === "Identifier" &&
declaration.name === "from" &&
declaration.end - declaration.start === 4 && // does not contain escape
!declaration.extra?.parenthesized
) {
this.raise(declaration.start, Errors.ExportDefaultFromAsIdentifier);
}
}
} else if (node.specifiers && node.specifiers.length) {
// Named exports
for (const specifier of node.specifiers) {

View File

@@ -251,6 +251,23 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return super.parseExport(node);
}
isExportDefaultSpecifier(): boolean {
if (this.match(tt._default)) {
const next = this.nextTokenStart();
if (this.isUnparsedContextual(next, "from")) {
if (
this.input.startsWith(
tt.placeholder.label,
this.nextTokenStartSince(next + 4),
)
) {
return true;
}
}
}
return super.isExportDefaultSpecifier();
}
maybeParseExportDefaultSpecifier(node: N.Node): boolean {
if (node.specifiers && node.specifiers.length > 0) {
// "export %%NAME%%" has already been parsed by #parseExport.

View File

@@ -190,11 +190,14 @@ export default class Tokenizer extends ParserErrors {
}
nextTokenStart(): number {
const thisTokEnd = this.state.pos;
skipWhiteSpace.lastIndex = thisTokEnd;
return this.nextTokenStartSince(this.state.pos);
}
nextTokenStartSince(pos: number): number {
skipWhiteSpace.lastIndex = pos;
const skip = skipWhiteSpace.exec(this.input);
// $FlowIgnore: The skipWhiteSpace ensures to match any string
return thisTokEnd + skip[0].length;
return pos + skip[0].length;
}
lookaheadCharCode(): number {