Simplifiy tracking of valid JSX positions (#13891)

Remove `state.inPropertyName` and simplifies `state.canStartJSXElement` tracking
This commit is contained in:
Huáng Jùnliàng 2021-11-04 12:12:42 -04:00 committed by GitHub
parent de28707dfe
commit 7250d2562b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 32 additions and 37 deletions

View File

@ -22,12 +22,12 @@ import {
tokenCanStartExpression, tokenCanStartExpression,
tokenIsAssignment, tokenIsAssignment,
tokenIsIdentifier, tokenIsIdentifier,
tokenIsKeyword,
tokenIsKeywordOrIdentifier, tokenIsKeywordOrIdentifier,
tokenIsOperator, tokenIsOperator,
tokenIsPostfix, tokenIsPostfix,
tokenIsPrefix, tokenIsPrefix,
tokenIsRightAssociative, tokenIsRightAssociative,
tokenKeywordOrIdentifierIsKeyword,
tokenLabelName, tokenLabelName,
tokenOperatorPrecedence, tokenOperatorPrecedence,
tt, tt,
@ -2237,8 +2237,6 @@ export default class ExpressionParser extends LValParser {
prop.key = this.parseMaybeAssignAllowIn(); prop.key = this.parseMaybeAssignAllowIn();
this.expect(tt.bracketR); this.expect(tt.bracketR);
} else { } else {
const oldInPropertyName = this.state.inPropertyName;
this.state.inPropertyName = true;
// We check if it's valid for it to be a private name when we push it. // We check if it's valid for it to be a private name when we push it.
const type = this.state.type; const type = this.state.type;
(prop: $FlowFixMe).key = (prop: $FlowFixMe).key =
@ -2253,8 +2251,6 @@ export default class ExpressionParser extends LValParser {
// ClassPrivateProperty is never computed, so we don't assign in that case. // ClassPrivateProperty is never computed, so we don't assign in that case.
prop.computed = false; prop.computed = false;
} }
this.state.inPropertyName = oldInPropertyName;
} }
return prop.key; return prop.key;
@ -2584,12 +2580,16 @@ export default class ExpressionParser extends LValParser {
throw this.unexpected(); throw this.unexpected();
} }
const tokenIsKeyword = tokenKeywordOrIdentifierIsKeyword(type);
if (liberal) { if (liberal) {
// If the current token is not used as a keyword, set its type to "tt.name". // If the current token is not used as a keyword, set its type to "tt.name".
// This will prevent this.next() from throwing about unexpected escapes. // This will prevent this.next() from throwing about unexpected escapes.
this.state.type = tt.name; if (tokenIsKeyword) {
this.replaceToken(tt.name);
}
} else { } else {
this.checkReservedWord(name, start, tokenIsKeyword(type), false); this.checkReservedWord(name, start, tokenIsKeyword, false);
} }
this.next(); this.next();

View File

@ -21,10 +21,6 @@ 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, makeErrorTemplates, ErrorCodes } from "../../parser/error"; import { Errors, makeErrorTemplates, ErrorCodes } from "../../parser/error";
import type { LookaheadState } from "../../tokenizer/state";
import State from "../../tokenizer/state";
type JSXLookaheadState = LookaheadState & { inPropertyName: boolean };
const HEX_NUMBER = /^[\da-fA-F]+$/; const HEX_NUMBER = /^[\da-fA-F]+$/;
const DECIMAL_NUMBER = /^\d+$/; const DECIMAL_NUMBER = /^\d+$/;
@ -106,7 +102,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
case charCodes.lessThan: case charCodes.lessThan:
case charCodes.leftCurlyBrace: case charCodes.leftCurlyBrace:
if (this.state.pos === this.state.start) { if (this.state.pos === this.state.start) {
if (ch === charCodes.lessThan && this.state.exprAllowed) { if (ch === charCodes.lessThan && this.state.canStartJSXElement) {
++this.state.pos; ++this.state.pos;
return this.finishToken(tt.jsxTagStart); return this.finishToken(tt.jsxTagStart);
} }
@ -556,24 +552,14 @@ export default (superClass: Class<Parser>): Class<Parser> =>
) { ) {
// In case we encounter an lt token here it will always be the start of // In case we encounter an lt token here it will always be the start of
// jsx as the lt sign is not allowed in places that expect an expression // jsx as the lt sign is not allowed in places that expect an expression
this.finishToken(tt.jsxTagStart); this.replaceToken(tt.jsxTagStart);
return this.jsxParseElement(); return this.jsxParseElement();
} else { } else {
return super.parseExprAtom(refExpressionErrors); return super.parseExprAtom(refExpressionErrors);
} }
} }
createLookaheadState(state: State): JSXLookaheadState {
const lookaheadState = ((super.createLookaheadState(
state,
): any): JSXLookaheadState);
lookaheadState.inPropertyName = state.inPropertyName;
return lookaheadState;
}
getTokenFromCode(code: number): void { getTokenFromCode(code: number): void {
if (this.state.inPropertyName) return super.getTokenFromCode(code);
const context = this.curContext(); const context = this.curContext();
if (context === tc.j_expr) { if (context === tc.j_expr) {
@ -600,7 +586,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
if ( if (
code === charCodes.lessThan && code === charCodes.lessThan &&
this.state.exprAllowed && this.state.canStartJSXElement &&
this.input.charCodeAt(this.state.pos + 1) !== charCodes.exclamationMark this.input.charCodeAt(this.state.pos + 1) !== charCodes.exclamationMark
) { ) {
++this.state.pos; ++this.state.pos;
@ -617,7 +603,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// do not consider JSX expr -> JSX open tag -> ... anymore // do not consider JSX expr -> JSX open tag -> ... anymore
// reconsider as closing tag context // reconsider as closing tag context
context.splice(-2, 2, tc.j_cTag); context.splice(-2, 2, tc.j_cTag);
this.state.exprAllowed = false; this.state.canStartJSXElement = false;
} else if (type === tt.jsxTagStart) { } else if (type === tt.jsxTagStart) {
context.push( context.push(
tc.j_expr, // treat as beginning of JSX expression tc.j_expr, // treat as beginning of JSX expression
@ -627,17 +613,13 @@ export default (superClass: Class<Parser>): Class<Parser> =>
const out = context.pop(); const out = context.pop();
if ((out === tc.j_oTag && prevType === tt.slash) || out === tc.j_cTag) { if ((out === tc.j_oTag && prevType === tt.slash) || out === tc.j_cTag) {
context.pop(); context.pop();
this.state.exprAllowed = context[context.length - 1] === tc.j_expr; this.state.canStartJSXElement =
context[context.length - 1] === tc.j_expr;
} else { } else {
this.state.exprAllowed = true; this.state.canStartJSXElement = true;
} }
} else if (
tokenIsKeyword(type) &&
(prevType === tt.dot || prevType === tt.questionDot)
) {
this.state.exprAllowed = false;
} else { } else {
this.state.exprAllowed = tokenComesBeforeExpression(type); this.state.canStartJSXElement = tokenComesBeforeExpression(type);
} }
} }
}; };

View File

@ -2125,7 +2125,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
// When ! is consumed as a postfix operator (non-null assertion), // When ! is consumed as a postfix operator (non-null assertion),
// disallow JSX tag forming after. e.g. When parsing `p! < n.p!` // disallow JSX tag forming after. e.g. When parsing `p! < n.p!`
// `<n.p` can not be a start of JSX tag // `<n.p` can not be a start of JSX tag
this.state.exprAllowed = false; this.state.canStartJSXElement = false;
this.next(); this.next();
const nonNullExpression: N.TsNonNullExpression = this.startNodeAt( const nonNullExpression: N.TsNonNullExpression = this.startNodeAt(

View File

@ -482,7 +482,7 @@ export default class Tokenizer extends ParserErrors {
} }
// Called at the end of every token. Sets `end`, `val`, and // Called at the end of every token. Sets `end`, `val`, and
// maintains `context` and `exprAllowed`, and skips the space after // maintains `context` and `canStartJSXElement`, and skips the space after
// the token, so that the next one's `start` will point at the // the token, so that the next one's `start` will point at the
// right position. // right position.
@ -498,6 +498,14 @@ export default class Tokenizer extends ParserErrors {
} }
} }
replaceToken(type: TokenType): void {
this.state.type = type;
// the prevType of updateContext is required
// only when the new type is tt.slash/tt.jsxTagEnd
// $FlowIgnore
this.updateContext();
}
// ### Token reading // ### Token reading
// This is the function that is called to fetch the next token. It // This is the function that is called to fetch the next token. It

View File

@ -68,7 +68,6 @@ export default class State {
maybeInArrowParameters: boolean = false; maybeInArrowParameters: boolean = false;
inType: boolean = false; inType: boolean = false;
noAnonFunctionType: boolean = false; noAnonFunctionType: boolean = false;
inPropertyName: boolean = false;
hasFlowComment: boolean = false; hasFlowComment: boolean = false;
isAmbientContext: boolean = false; isAmbientContext: boolean = false;
inAbstractClass: boolean = false; inAbstractClass: boolean = false;
@ -127,7 +126,7 @@ export default class State {
// or ends a string template // or ends a string template
context: Array<TokContext> = [ct.brace]; context: Array<TokContext> = [ct.brace];
// Used to track whether a JSX element is allowed to form // Used to track whether a JSX element is allowed to form
exprAllowed: boolean = true; canStartJSXElement: boolean = true;
// Used to signal to callers of `readWord1` whether the word // Used to signal to callers of `readWord1` whether the word
// contained any escape sequences. This is needed because words with // contained any escape sequences. This is needed because words with

View File

@ -332,6 +332,12 @@ export function tokenIsIdentifier(token: TokenType): boolean {
return token >= tt._as && token <= tt.name; return token >= tt._as && token <= tt.name;
} }
export function tokenKeywordOrIdentifierIsKeyword(token: TokenType): boolean {
// we can remove the token >= tt._in check when we
// know a token is either keyword or identifier
return token <= tt._while;
}
export function tokenIsKeywordOrIdentifier(token: TokenType): boolean { export function tokenIsKeywordOrIdentifier(token: TokenType): boolean {
return token >= tt._in && token <= tt.name; return token >= tt._in && token <= tt.name;
} }