mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-19 20:37:00 -05:00
Scanner / parser for JSX and As
This commit is contained in:
@@ -162,6 +162,9 @@ namespace ts {
|
||||
return visitNode(cbNode, (<BinaryExpression>node).left) ||
|
||||
visitNode(cbNode, (<BinaryExpression>node).operatorToken) ||
|
||||
visitNode(cbNode, (<BinaryExpression>node).right);
|
||||
case SyntaxKind.AsExpression:
|
||||
return visitNode(cbNode, (<AsExpression>node).expression) ||
|
||||
visitNode(cbNode, (<AsExpression>node).type);
|
||||
case SyntaxKind.ConditionalExpression:
|
||||
return visitNode(cbNode, (<ConditionalExpression>node).condition) ||
|
||||
visitNode(cbNode, (<ConditionalExpression>node).questionToken) ||
|
||||
@@ -319,6 +322,25 @@ namespace ts {
|
||||
return visitNode(cbNode, (<ExternalModuleReference>node).expression);
|
||||
case SyntaxKind.MissingDeclaration:
|
||||
return visitNodes(cbNodes, node.decorators);
|
||||
|
||||
case SyntaxKind.JsxElement:
|
||||
return visitNode(cbNode, (<JsxElement>node).openingElement) ||
|
||||
visitNodes(cbNodes, (<JsxElement>node).children) ||
|
||||
visitNode(cbNode, (<JsxElement>node).closingElement);
|
||||
case SyntaxKind.JsxSelfClosingElement:
|
||||
case SyntaxKind.JsxOpeningElement:
|
||||
return visitNode(cbNode, (<JsxOpeningLikeElement>node).tagName) ||
|
||||
visitNodes(cbNodes, (<JsxOpeningLikeElement>node).attributes);
|
||||
case SyntaxKind.JsxAttribute:
|
||||
return visitNode(cbNode, (<JsxAttribute>node).name) ||
|
||||
visitNode(cbNode, (<JsxAttribute>node).initializer);
|
||||
case SyntaxKind.JsxSpreadAttribute:
|
||||
return visitNode(cbNode, (<JsxSpreadAttribute>node).expression);
|
||||
case SyntaxKind.JsxExpression:
|
||||
return visitNode(cbNode, (<JsxExpression>node).expression);
|
||||
case SyntaxKind.JsxClosingElement:
|
||||
return visitNode(cbNode, (<JsxClosingElement>node).tagName);
|
||||
|
||||
case SyntaxKind.JSDocTypeExpression:
|
||||
return visitNode(cbNode, (<JSDocTypeExpression>node).type);
|
||||
case SyntaxKind.JSDocUnionType:
|
||||
@@ -634,6 +656,7 @@ namespace ts {
|
||||
sourceFile.languageVersion = languageVersion;
|
||||
sourceFile.fileName = normalizePath(fileName);
|
||||
sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0;
|
||||
sourceFile.isTSXFile = fileExtensionIs(sourceFile.fileName, ".tsx");
|
||||
|
||||
return sourceFile;
|
||||
}
|
||||
@@ -804,6 +827,10 @@ namespace ts {
|
||||
return token = scanner.reScanTemplateToken();
|
||||
}
|
||||
|
||||
function scanJsxIdentifier(): SyntaxKind {
|
||||
return token = scanner.scanJsxIdentifier();
|
||||
}
|
||||
|
||||
function speculationHelper<T>(callback: () => T, isLookAhead: boolean): T {
|
||||
// Keep track of the state we'll need to rollback to if lookahead fails (or if the
|
||||
// caller asked us to always reset our state).
|
||||
@@ -1175,6 +1202,10 @@ namespace ts {
|
||||
return isHeritageClause();
|
||||
case ParsingContext.ImportOrExportSpecifiers:
|
||||
return isIdentifierOrKeyword();
|
||||
case ParsingContext.JsxAttributes:
|
||||
return isIdentifierOrKeyword() || token === SyntaxKind.OpenBraceToken;
|
||||
case ParsingContext.JsxChildren:
|
||||
return token === SyntaxKind.LessThanToken || token === SyntaxKind.OpenBraceToken || token === SyntaxKind.JsxText;
|
||||
case ParsingContext.JSDocFunctionParameters:
|
||||
case ParsingContext.JSDocTypeArguments:
|
||||
case ParsingContext.JSDocTupleTypes:
|
||||
@@ -1265,6 +1296,11 @@ namespace ts {
|
||||
return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken;
|
||||
case ParsingContext.HeritageClauses:
|
||||
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.CloseBraceToken;
|
||||
case ParsingContext.JsxAttributes:
|
||||
// For error recovery, include } here (otherwise an over-braced {expr}} will close the surrounding statement block and mess up the entire file).
|
||||
return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.SlashToken || token === SyntaxKind.CloseBraceToken;
|
||||
case ParsingContext.JsxChildren:
|
||||
return token === SyntaxKind.LessThanSlashToken;
|
||||
case ParsingContext.JSDocFunctionParameters:
|
||||
return token === SyntaxKind.CloseParenToken || token === SyntaxKind.ColonToken || token === SyntaxKind.CloseBraceToken;
|
||||
case ParsingContext.JSDocTypeArguments:
|
||||
@@ -1481,6 +1517,12 @@ namespace ts {
|
||||
// name list, and there can be left hand side expressions (which can have type
|
||||
// arguments.)
|
||||
case ParsingContext.HeritageClauseElement:
|
||||
|
||||
// Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes
|
||||
// on any given element. Same for children.
|
||||
case ParsingContext.JsxAttributes:
|
||||
case ParsingContext.JsxChildren:
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1639,6 +1681,8 @@ namespace ts {
|
||||
case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected;
|
||||
case ParsingContext.HeritageClauses: return Diagnostics.Unexpected_token_expected;
|
||||
case ParsingContext.ImportOrExportSpecifiers: return Diagnostics.Identifier_expected;
|
||||
case ParsingContext.JsxAttributes: return Diagnostics.Identifier_expected;
|
||||
case ParsingContext.JsxChildren: return Diagnostics.Identifier_expected;
|
||||
case ParsingContext.JSDocFunctionParameters: return Diagnostics.Parameter_declaration_expected;
|
||||
case ParsingContext.JSDocTypeArguments: return Diagnostics.Type_argument_expected;
|
||||
case ParsingContext.JSDocTupleTypes: return Diagnostics.Type_expected;
|
||||
@@ -2794,6 +2838,32 @@ namespace ts {
|
||||
return Tristate.False;
|
||||
}
|
||||
|
||||
// JSX overrides
|
||||
if (sourceFile.isTSXFile) {
|
||||
let isArrowFunctionInJsx = lookAhead(() => {
|
||||
let third = nextToken();
|
||||
let fourth = nextToken();
|
||||
if (third === SyntaxKind.ExtendsKeyword) {
|
||||
switch (fourth) {
|
||||
case SyntaxKind.EqualsToken:
|
||||
case SyntaxKind.GreaterThanToken:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (third === SyntaxKind.CommaToken) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (isArrowFunctionInJsx) {
|
||||
return Tristate.Unknown;
|
||||
} else {
|
||||
return Tristate.False;
|
||||
}
|
||||
}
|
||||
|
||||
// This *could* be a parenthesized arrow function.
|
||||
return Tristate.Unknown;
|
||||
}
|
||||
@@ -2910,7 +2980,23 @@ namespace ts {
|
||||
break;
|
||||
}
|
||||
|
||||
leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence));
|
||||
if (token === SyntaxKind.AsKeyword) {
|
||||
// Make sure we *do* perform ASI for constructs like this:
|
||||
// var x = foo
|
||||
// as (Bar)
|
||||
// This should be parsed as an initialized variable, followed
|
||||
// by a function call to 'as' with the argument 'Bar'
|
||||
if (scanner.hasPrecedingLineBreak()) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
nextToken();
|
||||
leftOperand = makeAsExpression(leftOperand, parseType());
|
||||
}
|
||||
}
|
||||
else {
|
||||
leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence));
|
||||
}
|
||||
}
|
||||
|
||||
return leftOperand;
|
||||
@@ -2947,6 +3033,7 @@ namespace ts {
|
||||
case SyntaxKind.GreaterThanEqualsToken:
|
||||
case SyntaxKind.InstanceOfKeyword:
|
||||
case SyntaxKind.InKeyword:
|
||||
case SyntaxKind.AsKeyword:
|
||||
return 7;
|
||||
case SyntaxKind.LessThanLessThanToken:
|
||||
case SyntaxKind.GreaterThanGreaterThanToken:
|
||||
@@ -2974,6 +3061,13 @@ namespace ts {
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function makeAsExpression(left: Expression, right: TypeNode): AsExpression {
|
||||
let node = <AsExpression>createNode(SyntaxKind.AsExpression, left.pos);
|
||||
node.expression = left;
|
||||
node.type = right;
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parsePrefixUnaryExpression() {
|
||||
let node = <PrefixUnaryExpression>createNode(SyntaxKind.PrefixUnaryExpression);
|
||||
node.operator = token;
|
||||
@@ -3019,7 +3113,7 @@ namespace ts {
|
||||
case SyntaxKind.VoidKeyword:
|
||||
return parseVoidExpression();
|
||||
case SyntaxKind.LessThanToken:
|
||||
return parseTypeAssertion();
|
||||
return sourceFile.isTSXFile ? parseJsxElementOrSelfClosingElement() : parseTypeAssertion();
|
||||
default:
|
||||
return parsePostfixExpressionOrHigher();
|
||||
}
|
||||
@@ -3146,6 +3240,136 @@ namespace ts {
|
||||
node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseJsxChild(): JsxChild {
|
||||
let result: JsxChild = undefined;
|
||||
switch (token) {
|
||||
case SyntaxKind.JsxText:
|
||||
result = <JsxText>createNode(SyntaxKind.JsxText);
|
||||
token = scanner.scanJsxToken();
|
||||
result = finishNode(result);
|
||||
break;
|
||||
case SyntaxKind.OpenBraceToken:
|
||||
result = parseJsxExpression();
|
||||
break;
|
||||
default:
|
||||
Debug.assert(token === SyntaxKind.LessThanToken);
|
||||
result = parseJsxElementOrSelfClosingElement();
|
||||
break;
|
||||
}
|
||||
token = scanner.reScanJsxToken();
|
||||
Debug.assert(result !== undefined, "parsed some JSX child");
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseJsxElementOrSelfClosingElement(): JsxElement|JsxSelfClosingElement {
|
||||
let opening = parseJsxOpeningOrSelfClosingElement();
|
||||
if (opening.kind === SyntaxKind.JsxOpeningElement) {
|
||||
let node = <JsxElement>createNode(SyntaxKind.JsxElement, opening.pos);
|
||||
node.openingElement = opening;
|
||||
|
||||
// Rescan since parsing the > messed up the scanner state
|
||||
token = scanner.reScanJsxToken();
|
||||
node.children = parseList(ParsingContext.JsxChildren, parseJsxChild);
|
||||
node.closingElement = parseJsxClosingElement();
|
||||
return finishNode(node);
|
||||
}
|
||||
else {
|
||||
Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement);
|
||||
// Nothing else to do for self-closing elements
|
||||
return <JsxSelfClosingElement>opening;
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsxOpeningOrSelfClosingElement(): JsxOpeningElement|JsxSelfClosingElement {
|
||||
let fullStart = scanner.getStartPos();
|
||||
|
||||
parseExpected(SyntaxKind.LessThanToken);
|
||||
|
||||
let tagName = parseJsxElementName();
|
||||
let attributes = parseList(ParsingContext.JsxAttributes, parseJsxAttribute);
|
||||
let node: JsxOpeningLikeElement;
|
||||
if (token === SyntaxKind.SlashToken) {
|
||||
node = <JsxSelfClosingElement>createNode(SyntaxKind.JsxSelfClosingElement, fullStart);
|
||||
|
||||
nextToken();
|
||||
}
|
||||
else {
|
||||
node = <JsxOpeningElement>createNode(SyntaxKind.JsxOpeningElement, fullStart);
|
||||
}
|
||||
parseExpected(SyntaxKind.GreaterThanToken);
|
||||
|
||||
node.tagName = tagName;
|
||||
node.attributes = attributes;
|
||||
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseJsxElementName(): EntityName {
|
||||
scanJsxIdentifier();
|
||||
let elementName: EntityName = parseIdentifier();
|
||||
while (parseOptional(SyntaxKind.DotToken)) {
|
||||
scanJsxIdentifier();
|
||||
let node = <QualifiedName>createNode(SyntaxKind.QualifiedName, elementName.pos);
|
||||
node.left = elementName;
|
||||
node.right = parseIdentifierName();
|
||||
elementName = finishNode(node);
|
||||
}
|
||||
return elementName;
|
||||
}
|
||||
|
||||
function parseJsxExpression(): JsxExpression {
|
||||
let node = <JsxExpression>createNode(SyntaxKind.JsxExpression);
|
||||
|
||||
parseExpected(SyntaxKind.OpenBraceToken);
|
||||
if (token !== SyntaxKind.CloseBraceToken) {
|
||||
node.expression = parseExpression();
|
||||
}
|
||||
parseExpected(SyntaxKind.CloseBraceToken);
|
||||
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute {
|
||||
if (token === SyntaxKind.OpenBraceToken) {
|
||||
return parseJsxSpreadAttribute();
|
||||
}
|
||||
|
||||
scanJsxIdentifier();
|
||||
let node = <JsxAttribute>createNode(SyntaxKind.JsxAttribute);
|
||||
node.name = parseIdentifierName();
|
||||
if (parseOptional(SyntaxKind.EqualsToken)) {
|
||||
switch (token) {
|
||||
case SyntaxKind.LessThanToken:
|
||||
node.initializer = parseJsxElementOrSelfClosingElement();
|
||||
break;
|
||||
case SyntaxKind.StringLiteral:
|
||||
node.initializer = parseLiteralNode();
|
||||
break;
|
||||
default:
|
||||
node.initializer = parseJsxExpression();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseJsxSpreadAttribute(): JsxSpreadAttribute {
|
||||
let node = <JsxSpreadAttribute>createNode(SyntaxKind.JsxSpreadAttribute);
|
||||
parseExpected(SyntaxKind.OpenBraceToken);
|
||||
parseExpected(SyntaxKind.DotDotDotToken);
|
||||
node.expression = parseExpression();
|
||||
parseExpected(SyntaxKind.CloseBraceToken);
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseJsxClosingElement(): JsxClosingElement {
|
||||
let node = <JsxClosingElement>createNode(SyntaxKind.JsxClosingElement);
|
||||
parseExpected(SyntaxKind.LessThanSlashToken);
|
||||
node.tagName = parseJsxElementName();
|
||||
parseExpected(SyntaxKind.GreaterThanToken);
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
function parseTypeAssertion(): TypeAssertion {
|
||||
let node = <TypeAssertion>createNode(SyntaxKind.TypeAssertionExpression);
|
||||
@@ -4881,6 +5105,8 @@ namespace ts {
|
||||
ArrayBindingElements, // Binding elements in array binding list
|
||||
ArgumentExpressions, // Expressions in argument list
|
||||
ObjectLiteralMembers, // Members in object literal
|
||||
JsxAttributes, // Attributes in jsx element
|
||||
JsxChildren, // Things between opening and closing JSX tags
|
||||
ArrayLiteralMembers, // Members in array literal
|
||||
Parameters, // Parameters in parameter list
|
||||
TypeParameters, // Type parameters in type parameter list
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace ts {
|
||||
reScanGreaterToken(): SyntaxKind;
|
||||
reScanSlashToken(): SyntaxKind;
|
||||
reScanTemplateToken(): SyntaxKind;
|
||||
scanJsxIdentifier(): SyntaxKind;
|
||||
reScanJsxToken(): SyntaxKind;
|
||||
scanJsxToken(): SyntaxKind;
|
||||
scan(): SyntaxKind;
|
||||
// Sets the text for the scanner to scan. An optional subrange starting point and length
|
||||
// can be provided to have the scanner only scan a portion of the text.
|
||||
@@ -130,6 +133,7 @@ namespace ts {
|
||||
"++": SyntaxKind.PlusPlusToken,
|
||||
"--": SyntaxKind.MinusMinusToken,
|
||||
"<<": SyntaxKind.LessThanLessThanToken,
|
||||
"</": SyntaxKind.LessThanSlashToken,
|
||||
">>": SyntaxKind.GreaterThanGreaterThanToken,
|
||||
">>>": SyntaxKind.GreaterThanGreaterThanGreaterThanToken,
|
||||
"&": SyntaxKind.AmpersandToken,
|
||||
@@ -621,7 +625,7 @@ namespace ts {
|
||||
ch >= CharacterCodes._0 && ch <= CharacterCodes._9 || ch === CharacterCodes.$ || ch === CharacterCodes._ ||
|
||||
ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch, languageVersion);
|
||||
}
|
||||
|
||||
|
||||
/* @internal */
|
||||
// Creates a scanner over a (possibly unspecified) range of a piece of text.
|
||||
export function createScanner(languageVersion: ScriptTarget,
|
||||
@@ -665,6 +669,9 @@ namespace ts {
|
||||
reScanGreaterToken,
|
||||
reScanSlashToken,
|
||||
reScanTemplateToken,
|
||||
scanJsxIdentifier,
|
||||
reScanJsxToken,
|
||||
scanJsxToken,
|
||||
scan,
|
||||
setText,
|
||||
setScriptTarget,
|
||||
@@ -1481,6 +1488,62 @@ namespace ts {
|
||||
return token = scanTemplateAndSetTokenValue();
|
||||
}
|
||||
|
||||
function reScanJsxToken(): SyntaxKind {
|
||||
pos = tokenPos = startPos;
|
||||
return token = scanJsxToken();
|
||||
}
|
||||
|
||||
function scanJsxToken(): SyntaxKind {
|
||||
startPos = tokenPos = pos;
|
||||
|
||||
if (pos >= end) {
|
||||
return token = SyntaxKind.EndOfFileToken;
|
||||
}
|
||||
|
||||
let char = text.charCodeAt(pos);
|
||||
if (char === CharacterCodes.lessThan) {
|
||||
if (text.charCodeAt(pos + 1) === CharacterCodes.slash) {
|
||||
pos += 2;
|
||||
return token = SyntaxKind.LessThanSlashToken;
|
||||
}
|
||||
pos++;
|
||||
return token = SyntaxKind.LessThanToken;
|
||||
}
|
||||
|
||||
if (char === CharacterCodes.openBrace) {
|
||||
pos++;
|
||||
return token = SyntaxKind.OpenBraceToken;
|
||||
}
|
||||
|
||||
while (pos < end) {
|
||||
pos++;
|
||||
char = text.charCodeAt(pos);
|
||||
if ((char === CharacterCodes.openBrace) || (char === CharacterCodes.lessThan)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return token = SyntaxKind.JsxText;
|
||||
}
|
||||
|
||||
// Scans a JSX identifier; these differ from normal identifiers in that
|
||||
// they allow dashes
|
||||
function scanJsxIdentifier(): SyntaxKind {
|
||||
if (token === SyntaxKind.Identifier) {
|
||||
let firstCharPosition = pos;
|
||||
while (pos < end) {
|
||||
let ch = text.charCodeAt(pos);
|
||||
if (ch === CharacterCodes.minus || ((firstCharPosition === pos) ? isIdentifierStart(ch) : isIdentifierPart(ch))) {
|
||||
pos++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokenValue += text.substr(firstCharPosition, pos - firstCharPosition);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function speculationHelper<T>(callback: () => T, isLookahead: boolean): T {
|
||||
let savePos = pos;
|
||||
let saveStartPos = startPos;
|
||||
|
||||
Reference in New Issue
Block a user