6172 lines
296 KiB
TypeScript
6172 lines
296 KiB
TypeScript
/// <reference path="scanner.ts"/>
|
|
/// <reference path="utilities.ts"/>
|
|
|
|
module ts {
|
|
export var throwOnJSDocErrors = false;
|
|
|
|
let nodeConstructors = new Array<new () => Node>(SyntaxKind.Count);
|
|
/* @internal */ export let parseTime = 0;
|
|
|
|
export function getNodeConstructor(kind: SyntaxKind): new () => Node {
|
|
return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind));
|
|
}
|
|
|
|
export function createNode(kind: SyntaxKind): Node {
|
|
return new (getNodeConstructor(kind))();
|
|
}
|
|
|
|
function visitNode<T>(cbNode: (node: Node) => T, node: Node): T {
|
|
if (node) {
|
|
return cbNode(node);
|
|
}
|
|
}
|
|
|
|
function visitNodeArray<T>(cbNodes: (nodes: Node[]) => T, nodes: Node[]) {
|
|
if (nodes) {
|
|
return cbNodes(nodes);
|
|
}
|
|
}
|
|
|
|
function visitEachNode<T>(cbNode: (node: Node) => T, nodes: Node[]) {
|
|
if (nodes) {
|
|
for (let node of nodes) {
|
|
let result = cbNode(node);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes
|
|
// stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise,
|
|
// embedded arrays are flattened and the 'cbNode' callback is invoked for each element. If a callback returns
|
|
// a truthy value, iteration stops and that value is returned. Otherwise, undefined is returned.
|
|
export function forEachChild<T>(node: Node, cbNode: (node: Node) => T, cbNodeArray?: (nodes: Node[]) => T): T {
|
|
if (!node) {
|
|
return;
|
|
}
|
|
// The visitXXX functions could be written as local functions that close over the cbNode and cbNodeArray
|
|
// callback parameters, but that causes a closure allocation for each invocation with noticeable effects
|
|
// on performance.
|
|
let visitNodes: (cb: (node: Node | Node[]) => T, nodes: Node[]) => T = cbNodeArray ? visitNodeArray : visitEachNode;
|
|
let cbNodes = cbNodeArray || cbNode;
|
|
switch (node.kind) {
|
|
case SyntaxKind.QualifiedName:
|
|
return visitNode(cbNode, (<QualifiedName>node).left) ||
|
|
visitNode(cbNode, (<QualifiedName>node).right);
|
|
case SyntaxKind.TypeParameter:
|
|
return visitNode(cbNode, (<TypeParameterDeclaration>node).name) ||
|
|
visitNode(cbNode, (<TypeParameterDeclaration>node).constraint) ||
|
|
visitNode(cbNode, (<TypeParameterDeclaration>node).expression);
|
|
case SyntaxKind.Parameter:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.PropertyAssignment:
|
|
case SyntaxKind.ShorthandPropertyAssignment:
|
|
case SyntaxKind.VariableDeclaration:
|
|
case SyntaxKind.BindingElement:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<VariableLikeDeclaration>node).propertyName) ||
|
|
visitNode(cbNode, (<VariableLikeDeclaration>node).dotDotDotToken) ||
|
|
visitNode(cbNode, (<VariableLikeDeclaration>node).name) ||
|
|
visitNode(cbNode, (<VariableLikeDeclaration>node).questionToken) ||
|
|
visitNode(cbNode, (<VariableLikeDeclaration>node).type) ||
|
|
visitNode(cbNode, (<VariableLikeDeclaration>node).initializer);
|
|
case SyntaxKind.FunctionType:
|
|
case SyntaxKind.ConstructorType:
|
|
case SyntaxKind.CallSignature:
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNodes(cbNodes, (<SignatureDeclaration>node).typeParameters) ||
|
|
visitNodes(cbNodes, (<SignatureDeclaration>node).parameters) ||
|
|
visitNode(cbNode, (<SignatureDeclaration>node).type);
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.FunctionExpression:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.ArrowFunction:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<FunctionLikeDeclaration>node).asteriskToken) ||
|
|
visitNode(cbNode, (<FunctionLikeDeclaration>node).name) ||
|
|
visitNode(cbNode, (<FunctionLikeDeclaration>node).questionToken) ||
|
|
visitNodes(cbNodes, (<FunctionLikeDeclaration>node).typeParameters) ||
|
|
visitNodes(cbNodes, (<FunctionLikeDeclaration>node).parameters) ||
|
|
visitNode(cbNode, (<FunctionLikeDeclaration>node).type) ||
|
|
visitNode(cbNode, (<ArrowFunction>node).equalsGreaterThanToken) ||
|
|
visitNode(cbNode, (<FunctionLikeDeclaration>node).body);
|
|
case SyntaxKind.TypeReference:
|
|
return visitNode(cbNode, (<TypeReferenceNode>node).typeName) ||
|
|
visitNodes(cbNodes, (<TypeReferenceNode>node).typeArguments);
|
|
case SyntaxKind.TypeQuery:
|
|
return visitNode(cbNode, (<TypeQueryNode>node).exprName);
|
|
case SyntaxKind.TypeLiteral:
|
|
return visitNodes(cbNodes, (<TypeLiteralNode>node).members);
|
|
case SyntaxKind.ArrayType:
|
|
return visitNode(cbNode, (<ArrayTypeNode>node).elementType);
|
|
case SyntaxKind.TupleType:
|
|
return visitNodes(cbNodes, (<TupleTypeNode>node).elementTypes);
|
|
case SyntaxKind.UnionType:
|
|
return visitNodes(cbNodes, (<UnionTypeNode>node).types);
|
|
case SyntaxKind.ParenthesizedType:
|
|
return visitNode(cbNode, (<ParenthesizedTypeNode>node).type);
|
|
case SyntaxKind.ObjectBindingPattern:
|
|
case SyntaxKind.ArrayBindingPattern:
|
|
return visitNodes(cbNodes, (<BindingPattern>node).elements);
|
|
case SyntaxKind.ArrayLiteralExpression:
|
|
return visitNodes(cbNodes, (<ArrayLiteralExpression>node).elements);
|
|
case SyntaxKind.ObjectLiteralExpression:
|
|
return visitNodes(cbNodes, (<ObjectLiteralExpression>node).properties);
|
|
case SyntaxKind.PropertyAccessExpression:
|
|
return visitNode(cbNode, (<PropertyAccessExpression>node).expression) ||
|
|
visitNode(cbNode, (<PropertyAccessExpression>node).dotToken) ||
|
|
visitNode(cbNode, (<PropertyAccessExpression>node).name);
|
|
case SyntaxKind.ElementAccessExpression:
|
|
return visitNode(cbNode, (<ElementAccessExpression>node).expression) ||
|
|
visitNode(cbNode, (<ElementAccessExpression>node).argumentExpression);
|
|
case SyntaxKind.CallExpression:
|
|
case SyntaxKind.NewExpression:
|
|
return visitNode(cbNode, (<CallExpression>node).expression) ||
|
|
visitNodes(cbNodes, (<CallExpression>node).typeArguments) ||
|
|
visitNodes(cbNodes, (<CallExpression>node).arguments);
|
|
case SyntaxKind.TaggedTemplateExpression:
|
|
return visitNode(cbNode, (<TaggedTemplateExpression>node).tag) ||
|
|
visitNode(cbNode, (<TaggedTemplateExpression>node).template);
|
|
case SyntaxKind.TypeAssertionExpression:
|
|
return visitNode(cbNode, (<TypeAssertion>node).type) ||
|
|
visitNode(cbNode, (<TypeAssertion>node).expression);
|
|
case SyntaxKind.ParenthesizedExpression:
|
|
return visitNode(cbNode, (<ParenthesizedExpression>node).expression);
|
|
case SyntaxKind.DeleteExpression:
|
|
return visitNode(cbNode, (<DeleteExpression>node).expression);
|
|
case SyntaxKind.TypeOfExpression:
|
|
return visitNode(cbNode, (<TypeOfExpression>node).expression);
|
|
case SyntaxKind.VoidExpression:
|
|
return visitNode(cbNode, (<VoidExpression>node).expression);
|
|
case SyntaxKind.PrefixUnaryExpression:
|
|
return visitNode(cbNode, (<PrefixUnaryExpression>node).operand);
|
|
case SyntaxKind.YieldExpression:
|
|
return visitNode(cbNode, (<YieldExpression>node).asteriskToken) ||
|
|
visitNode(cbNode, (<YieldExpression>node).expression);
|
|
case SyntaxKind.PostfixUnaryExpression:
|
|
return visitNode(cbNode, (<PostfixUnaryExpression>node).operand);
|
|
case SyntaxKind.BinaryExpression:
|
|
return visitNode(cbNode, (<BinaryExpression>node).left) ||
|
|
visitNode(cbNode, (<BinaryExpression>node).operatorToken) ||
|
|
visitNode(cbNode, (<BinaryExpression>node).right);
|
|
case SyntaxKind.ConditionalExpression:
|
|
return visitNode(cbNode, (<ConditionalExpression>node).condition) ||
|
|
visitNode(cbNode, (<ConditionalExpression>node).questionToken) ||
|
|
visitNode(cbNode, (<ConditionalExpression>node).whenTrue) ||
|
|
visitNode(cbNode, (<ConditionalExpression>node).colonToken) ||
|
|
visitNode(cbNode, (<ConditionalExpression>node).whenFalse);
|
|
case SyntaxKind.SpreadElementExpression:
|
|
return visitNode(cbNode, (<SpreadElementExpression>node).expression);
|
|
case SyntaxKind.Block:
|
|
case SyntaxKind.ModuleBlock:
|
|
return visitNodes(cbNodes, (<Block>node).statements);
|
|
case SyntaxKind.SourceFile:
|
|
return visitNodes(cbNodes, (<SourceFile>node).statements) ||
|
|
visitNode(cbNode, (<SourceFile>node).endOfFileToken);
|
|
case SyntaxKind.VariableStatement:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<VariableStatement>node).declarationList);
|
|
case SyntaxKind.VariableDeclarationList:
|
|
return visitNodes(cbNodes, (<VariableDeclarationList>node).declarations);
|
|
case SyntaxKind.ExpressionStatement:
|
|
return visitNode(cbNode, (<ExpressionStatement>node).expression);
|
|
case SyntaxKind.IfStatement:
|
|
return visitNode(cbNode, (<IfStatement>node).expression) ||
|
|
visitNode(cbNode, (<IfStatement>node).thenStatement) ||
|
|
visitNode(cbNode, (<IfStatement>node).elseStatement);
|
|
case SyntaxKind.DoStatement:
|
|
return visitNode(cbNode, (<DoStatement>node).statement) ||
|
|
visitNode(cbNode, (<DoStatement>node).expression);
|
|
case SyntaxKind.WhileStatement:
|
|
return visitNode(cbNode, (<WhileStatement>node).expression) ||
|
|
visitNode(cbNode, (<WhileStatement>node).statement);
|
|
case SyntaxKind.ForStatement:
|
|
return visitNode(cbNode, (<ForStatement>node).initializer) ||
|
|
visitNode(cbNode, (<ForStatement>node).condition) ||
|
|
visitNode(cbNode, (<ForStatement>node).incrementor) ||
|
|
visitNode(cbNode, (<ForStatement>node).statement);
|
|
case SyntaxKind.ForInStatement:
|
|
return visitNode(cbNode, (<ForInStatement>node).initializer) ||
|
|
visitNode(cbNode, (<ForInStatement>node).expression) ||
|
|
visitNode(cbNode, (<ForInStatement>node).statement);
|
|
case SyntaxKind.ForOfStatement:
|
|
return visitNode(cbNode, (<ForOfStatement>node).initializer) ||
|
|
visitNode(cbNode, (<ForOfStatement>node).expression) ||
|
|
visitNode(cbNode, (<ForOfStatement>node).statement);
|
|
case SyntaxKind.ContinueStatement:
|
|
case SyntaxKind.BreakStatement:
|
|
return visitNode(cbNode, (<BreakOrContinueStatement>node).label);
|
|
case SyntaxKind.ReturnStatement:
|
|
return visitNode(cbNode, (<ReturnStatement>node).expression);
|
|
case SyntaxKind.WithStatement:
|
|
return visitNode(cbNode, (<WithStatement>node).expression) ||
|
|
visitNode(cbNode, (<WithStatement>node).statement);
|
|
case SyntaxKind.SwitchStatement:
|
|
return visitNode(cbNode, (<SwitchStatement>node).expression) ||
|
|
visitNode(cbNode, (<SwitchStatement>node).caseBlock);
|
|
case SyntaxKind.CaseBlock:
|
|
return visitNodes(cbNodes, (<CaseBlock>node).clauses);
|
|
case SyntaxKind.CaseClause:
|
|
return visitNode(cbNode, (<CaseClause>node).expression) ||
|
|
visitNodes(cbNodes, (<CaseClause>node).statements);
|
|
case SyntaxKind.DefaultClause:
|
|
return visitNodes(cbNodes, (<DefaultClause>node).statements);
|
|
case SyntaxKind.LabeledStatement:
|
|
return visitNode(cbNode, (<LabeledStatement>node).label) ||
|
|
visitNode(cbNode, (<LabeledStatement>node).statement);
|
|
case SyntaxKind.ThrowStatement:
|
|
return visitNode(cbNode, (<ThrowStatement>node).expression);
|
|
case SyntaxKind.TryStatement:
|
|
return visitNode(cbNode, (<TryStatement>node).tryBlock) ||
|
|
visitNode(cbNode, (<TryStatement>node).catchClause) ||
|
|
visitNode(cbNode, (<TryStatement>node).finallyBlock);
|
|
case SyntaxKind.CatchClause:
|
|
return visitNode(cbNode, (<CatchClause>node).variableDeclaration) ||
|
|
visitNode(cbNode, (<CatchClause>node).block);
|
|
case SyntaxKind.Decorator:
|
|
return visitNode(cbNode, (<Decorator>node).expression);
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.ClassExpression:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<ClassLikeDeclaration>node).name) ||
|
|
visitNodes(cbNodes, (<ClassLikeDeclaration>node).typeParameters) ||
|
|
visitNodes(cbNodes, (<ClassLikeDeclaration>node).heritageClauses) ||
|
|
visitNodes(cbNodes, (<ClassLikeDeclaration>node).members);
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<InterfaceDeclaration>node).name) ||
|
|
visitNodes(cbNodes, (<InterfaceDeclaration>node).typeParameters) ||
|
|
visitNodes(cbNodes, (<ClassDeclaration>node).heritageClauses) ||
|
|
visitNodes(cbNodes, (<InterfaceDeclaration>node).members);
|
|
case SyntaxKind.TypeAliasDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<TypeAliasDeclaration>node).name) ||
|
|
visitNode(cbNode, (<TypeAliasDeclaration>node).type);
|
|
case SyntaxKind.EnumDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<EnumDeclaration>node).name) ||
|
|
visitNodes(cbNodes, (<EnumDeclaration>node).members);
|
|
case SyntaxKind.EnumMember:
|
|
return visitNode(cbNode, (<EnumMember>node).name) ||
|
|
visitNode(cbNode, (<EnumMember>node).initializer);
|
|
case SyntaxKind.ModuleDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<ModuleDeclaration>node).name) ||
|
|
visitNode(cbNode, (<ModuleDeclaration>node).body);
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<ImportEqualsDeclaration>node).name) ||
|
|
visitNode(cbNode, (<ImportEqualsDeclaration>node).moduleReference);
|
|
case SyntaxKind.ImportDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<ImportDeclaration>node).importClause) ||
|
|
visitNode(cbNode, (<ImportDeclaration>node).moduleSpecifier);
|
|
case SyntaxKind.ImportClause:
|
|
return visitNode(cbNode, (<ImportClause>node).name) ||
|
|
visitNode(cbNode, (<ImportClause>node).namedBindings);
|
|
case SyntaxKind.NamespaceImport:
|
|
return visitNode(cbNode, (<NamespaceImport>node).name);
|
|
case SyntaxKind.NamedImports:
|
|
case SyntaxKind.NamedExports:
|
|
return visitNodes(cbNodes, (<NamedImportsOrExports>node).elements);
|
|
case SyntaxKind.ExportDeclaration:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<ExportDeclaration>node).exportClause) ||
|
|
visitNode(cbNode, (<ExportDeclaration>node).moduleSpecifier);
|
|
case SyntaxKind.ImportSpecifier:
|
|
case SyntaxKind.ExportSpecifier:
|
|
return visitNode(cbNode, (<ImportOrExportSpecifier>node).propertyName) ||
|
|
visitNode(cbNode, (<ImportOrExportSpecifier>node).name);
|
|
case SyntaxKind.ExportAssignment:
|
|
return visitNodes(cbNodes, node.decorators) ||
|
|
visitNodes(cbNodes, node.modifiers) ||
|
|
visitNode(cbNode, (<ExportAssignment>node).expression);
|
|
case SyntaxKind.TemplateExpression:
|
|
return visitNode(cbNode, (<TemplateExpression>node).head) || visitNodes(cbNodes, (<TemplateExpression>node).templateSpans);
|
|
case SyntaxKind.TemplateSpan:
|
|
return visitNode(cbNode, (<TemplateSpan>node).expression) || visitNode(cbNode, (<TemplateSpan>node).literal);
|
|
case SyntaxKind.ComputedPropertyName:
|
|
return visitNode(cbNode, (<ComputedPropertyName>node).expression);
|
|
case SyntaxKind.HeritageClause:
|
|
return visitNodes(cbNodes, (<HeritageClause>node).types);
|
|
case SyntaxKind.ExpressionWithTypeArguments:
|
|
return visitNode(cbNode, (<ExpressionWithTypeArguments>node).expression) ||
|
|
visitNodes(cbNodes, (<ExpressionWithTypeArguments>node).typeArguments);
|
|
case SyntaxKind.ExternalModuleReference:
|
|
return visitNode(cbNode, (<ExternalModuleReference>node).expression);
|
|
case SyntaxKind.MissingDeclaration:
|
|
return visitNodes(cbNodes, node.decorators);
|
|
case SyntaxKind.JSDocTypeExpression:
|
|
return visitNode(cbNode, (<JSDocTypeExpression>node).type);
|
|
case SyntaxKind.JSDocUnionType:
|
|
return visitNodes(cbNodes, (<JSDocUnionType>node).types);
|
|
case SyntaxKind.JSDocTupleType:
|
|
return visitNodes(cbNodes, (<JSDocTupleType>node).types);
|
|
case SyntaxKind.JSDocArrayType:
|
|
return visitNode(cbNode, (<JSDocArrayType>node).elementType);
|
|
case SyntaxKind.JSDocNonNullableType:
|
|
return visitNode(cbNode, (<JSDocNonNullableType>node).type);
|
|
case SyntaxKind.JSDocNullableType:
|
|
return visitNode(cbNode, (<JSDocNullableType>node).type);
|
|
case SyntaxKind.JSDocRecordType:
|
|
return visitNodes(cbNodes, (<JSDocRecordType>node).members);
|
|
case SyntaxKind.JSDocTypeReference:
|
|
return visitNode(cbNode, (<JSDocTypeReference>node).name) ||
|
|
visitNodes(cbNodes, (<JSDocTypeReference>node).typeArguments);
|
|
case SyntaxKind.JSDocOptionalType:
|
|
return visitNode(cbNode, (<JSDocOptionalType>node).type);
|
|
case SyntaxKind.JSDocFunctionType:
|
|
return visitNodes(cbNodes, (<JSDocFunctionType>node).parameters) ||
|
|
visitNode(cbNode, (<JSDocFunctionType>node).type);
|
|
case SyntaxKind.JSDocVariadicType:
|
|
return visitNode(cbNode, (<JSDocVariadicType>node).type);
|
|
case SyntaxKind.JSDocConstructorType:
|
|
return visitNode(cbNode, (<JSDocConstructorType>node).type);
|
|
case SyntaxKind.JSDocThisType:
|
|
return visitNode(cbNode, (<JSDocThisType>node).type);
|
|
case SyntaxKind.JSDocRecordMember:
|
|
return visitNode(cbNode, (<JSDocRecordMember>node).name) ||
|
|
visitNode(cbNode, (<JSDocRecordMember>node).type);
|
|
case SyntaxKind.JSDocComment:
|
|
return visitNodes(cbNodes, (<JSDocComment>node).tags);
|
|
case SyntaxKind.JSDocParameterTag:
|
|
return visitNode(cbNode, (<JSDocParameterTag>node).preParameterName) ||
|
|
visitNode(cbNode, (<JSDocParameterTag>node).typeExpression) ||
|
|
visitNode(cbNode, (<JSDocParameterTag>node).postParameterName);
|
|
case SyntaxKind.JSDocReturnTag:
|
|
return visitNode(cbNode, (<JSDocReturnTag>node).typeExpression);
|
|
case SyntaxKind.JSDocTypeTag:
|
|
return visitNode(cbNode, (<JSDocTypeTag>node).typeExpression);
|
|
case SyntaxKind.JSDocTemplateTag:
|
|
return visitNodes(cbNodes, (<JSDocTemplateTag>node).typeParameters);
|
|
}
|
|
}
|
|
|
|
export function createSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, setParentNodes = false): SourceFile {
|
|
let start = new Date().getTime();
|
|
let result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes);
|
|
|
|
parseTime += new Date().getTime() - start;
|
|
return result;
|
|
}
|
|
|
|
// Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter
|
|
// indicates what changed between the 'text' that this SourceFile has and the 'newText'.
|
|
// The SourceFile will be created with the compiler attempting to reuse as many nodes from
|
|
// this file as possible.
|
|
//
|
|
// Note: this function mutates nodes from this SourceFile. That means any existing nodes
|
|
// from this SourceFile that are being held onto may change as a result (including
|
|
// becoming detached from any SourceFile). It is recommended that this SourceFile not
|
|
// be used once 'update' is called on it.
|
|
export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile {
|
|
return IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks);
|
|
}
|
|
|
|
/* @internal */
|
|
export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) {
|
|
return Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length);
|
|
}
|
|
|
|
/* @internal */
|
|
// Exposed only for testing.
|
|
export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) {
|
|
return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length);
|
|
}
|
|
|
|
// Implement the parser as a singleton module. We do this for perf reasons because creating
|
|
// parser instances can actually be expensive enough to impact us on projects with many source
|
|
// files.
|
|
module Parser {
|
|
// Share a single scanner across all calls to parse a source file. This helps speed things
|
|
// up by avoiding the cost of creating/compiling scanners over and over again.
|
|
const scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true);
|
|
const disallowInAndDecoratorContext = ParserContextFlags.DisallowIn | ParserContextFlags.Decorator;
|
|
|
|
let sourceFile: SourceFile;
|
|
let parseDiagnostics: Diagnostic[];
|
|
let syntaxCursor: IncrementalParser.SyntaxCursor;
|
|
|
|
let token: SyntaxKind;
|
|
let sourceText: string;
|
|
let nodeCount: number;
|
|
let identifiers: Map<string>;
|
|
let identifierCount: number;
|
|
|
|
let parsingContext: ParsingContext;
|
|
|
|
// Flags that dictate what parsing context we're in. For example:
|
|
// Whether or not we are in strict parsing mode. All that changes in strict parsing mode is
|
|
// that some tokens that would be considered identifiers may be considered keywords.
|
|
//
|
|
// When adding more parser context flags, consider which is the more common case that the
|
|
// flag will be in. This should be the 'false' state for that flag. The reason for this is
|
|
// that we don't store data in our nodes unless the value is in the *non-default* state. So,
|
|
// for example, more often than code 'allows-in' (or doesn't 'disallow-in'). We opt for
|
|
// 'disallow-in' set to 'false'. Otherwise, if we had 'allowsIn' set to 'true', then almost
|
|
// all nodes would need extra state on them to store this info.
|
|
//
|
|
// Note: 'allowIn' and 'allowYield' track 1:1 with the [in] and [yield] concepts in the ES6
|
|
// grammar specification.
|
|
//
|
|
// An important thing about these context concepts. By default they are effectively inherited
|
|
// while parsing through every grammar production. i.e. if you don't change them, then when
|
|
// you parse a sub-production, it will have the same context values as the parent production.
|
|
// This is great most of the time. After all, consider all the 'expression' grammar productions
|
|
// and how nearly all of them pass along the 'in' and 'yield' context values:
|
|
//
|
|
// EqualityExpression[In, Yield] :
|
|
// RelationalExpression[?In, ?Yield]
|
|
// EqualityExpression[?In, ?Yield] == RelationalExpression[?In, ?Yield]
|
|
// EqualityExpression[?In, ?Yield] != RelationalExpression[?In, ?Yield]
|
|
// EqualityExpression[?In, ?Yield] === RelationalExpression[?In, ?Yield]
|
|
// EqualityExpression[?In, ?Yield] !== RelationalExpression[?In, ?Yield]
|
|
//
|
|
// Where you have to be careful is then understanding what the points are in the grammar
|
|
// where the values are *not* passed along. For example:
|
|
//
|
|
// SingleNameBinding[Yield,GeneratorParameter]
|
|
// [+GeneratorParameter]BindingIdentifier[Yield] Initializer[In]opt
|
|
// [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt
|
|
//
|
|
// Here this is saying that if the GeneratorParameter context flag is set, that we should
|
|
// explicitly set the 'yield' context flag to false before calling into the BindingIdentifier
|
|
// and we should explicitly unset the 'yield' context flag before calling into the Initializer.
|
|
// production. Conversely, if the GeneratorParameter context flag is not set, then we
|
|
// should leave the 'yield' context flag alone.
|
|
//
|
|
// Getting this all correct is tricky and requires careful reading of the grammar to
|
|
// understand when these values should be changed versus when they should be inherited.
|
|
//
|
|
// Note: it should not be necessary to save/restore these flags during speculative/lookahead
|
|
// parsing. These context flags are naturally stored and restored through normal recursive
|
|
// descent parsing and unwinding.
|
|
let contextFlags: ParserContextFlags;
|
|
|
|
// Whether or not we've had a parse error since creating the last AST node. If we have
|
|
// encountered an error, it will be stored on the next AST node we create. Parse errors
|
|
// can be broken down into three categories:
|
|
//
|
|
// 1) An error that occurred during scanning. For example, an unterminated literal, or a
|
|
// character that was completely not understood.
|
|
//
|
|
// 2) A token was expected, but was not present. This type of error is commonly produced
|
|
// by the 'parseExpected' function.
|
|
//
|
|
// 3) A token was present that no parsing function was able to consume. This type of error
|
|
// only occurs in the 'abortParsingListOrMoveToNextToken' function when the parser
|
|
// decides to skip the token.
|
|
//
|
|
// In all of these cases, we want to mark the next node as having had an error before it.
|
|
// With this mark, we can know in incremental settings if this node can be reused, or if
|
|
// we have to reparse it. If we don't keep this information around, we may just reuse the
|
|
// node. in that event we would then not produce the same errors as we did before, causing
|
|
// significant confusion problems.
|
|
//
|
|
// Note: it is necessary that this value be saved/restored during speculative/lookahead
|
|
// parsing. During lookahead parsing, we will often create a node. That node will have
|
|
// this value attached, and then this value will be set back to 'false'. If we decide to
|
|
// rewind, we must get back to the same value we had prior to the lookahead.
|
|
//
|
|
// Note: any errors at the end of the file that do not precede a regular node, should get
|
|
// attached to the EOF token.
|
|
let parseErrorBeforeNextFinishedNode: boolean = false;
|
|
|
|
export const enum StatementFlags {
|
|
None = 0,
|
|
Statement = 1,
|
|
ModuleElement = 2,
|
|
StatementOrModuleElement = Statement | ModuleElement
|
|
}
|
|
|
|
export function parseSourceFile(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor, setParentNodes?: boolean): SourceFile {
|
|
initializeState(fileName, _sourceText, languageVersion, _syntaxCursor);
|
|
|
|
let result = parseSourceFileWorker(fileName, languageVersion, setParentNodes);
|
|
|
|
clearState();
|
|
|
|
return result;
|
|
}
|
|
|
|
function initializeState(fileName: string, _sourceText: string, languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor) {
|
|
sourceText = _sourceText;
|
|
syntaxCursor = _syntaxCursor;
|
|
|
|
parseDiagnostics = [];
|
|
parsingContext = 0;
|
|
identifiers = {};
|
|
identifierCount = 0;
|
|
nodeCount = 0;
|
|
|
|
contextFlags = isJavaScript(fileName) ? ParserContextFlags.JavaScriptFile : ParserContextFlags.None;
|
|
parseErrorBeforeNextFinishedNode = false;
|
|
|
|
// Initialize and prime the scanner before parsing the source elements.
|
|
scanner.setText(sourceText);
|
|
scanner.setOnError(scanError);
|
|
scanner.setScriptTarget(languageVersion);
|
|
}
|
|
|
|
function clearState() {
|
|
// Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily.
|
|
scanner.setText("");
|
|
scanner.setOnError(undefined);
|
|
|
|
// Clear any data. We don't want to accidently hold onto it for too long.
|
|
parseDiagnostics = undefined;
|
|
sourceFile = undefined;
|
|
identifiers = undefined;
|
|
syntaxCursor = undefined;
|
|
sourceText = undefined;
|
|
}
|
|
|
|
function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean): SourceFile {
|
|
sourceFile = createSourceFile(fileName, languageVersion);
|
|
|
|
// Prime the scanner.
|
|
token = nextToken();
|
|
processReferenceComments(sourceFile);
|
|
|
|
sourceFile.statements = parseList(ParsingContext.SourceElements, /*checkForStrictMode*/ true, parseSourceElement);
|
|
Debug.assert(token === SyntaxKind.EndOfFileToken);
|
|
sourceFile.endOfFileToken = parseTokenNode();
|
|
|
|
setExternalModuleIndicator(sourceFile);
|
|
|
|
sourceFile.nodeCount = nodeCount;
|
|
sourceFile.identifierCount = identifierCount;
|
|
sourceFile.identifiers = identifiers;
|
|
sourceFile.parseDiagnostics = parseDiagnostics;
|
|
|
|
if (setParentNodes) {
|
|
fixupParentReferences(sourceFile);
|
|
}
|
|
|
|
// If this is a javascript file, proactively see if we can get JSDoc comments for
|
|
// relevant nodes in the file. We'll use these to provide typing informaion if they're
|
|
// available.
|
|
if (isJavaScript(fileName)) {
|
|
addJSDocComments();
|
|
}
|
|
|
|
return sourceFile;
|
|
}
|
|
|
|
function addJSDocComments() {
|
|
forEachChild(sourceFile, visit);
|
|
return;
|
|
|
|
function visit(node: Node) {
|
|
// Add additional cases as necessary depending on how we see JSDoc comments used
|
|
// in the wild.
|
|
switch (node.kind) {
|
|
case SyntaxKind.VariableStatement:
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.Parameter:
|
|
addJSDocComment(node);
|
|
}
|
|
|
|
forEachChild(node, visit);
|
|
}
|
|
}
|
|
|
|
function addJSDocComment(node: Node) {
|
|
let comments = getLeadingCommentRangesOfNode(node, sourceFile);
|
|
if (comments) {
|
|
for (let comment of comments) {
|
|
let jsDocComment = JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos);
|
|
if (jsDocComment) {
|
|
node.jsDocComment = jsDocComment;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export function fixupParentReferences(sourceFile: Node) {
|
|
// normally parent references are set during binding. However, for clients that only need
|
|
// a syntax tree, and no semantic features, then the binding process is an unnecessary
|
|
// overhead. This functions allows us to set all the parents, without all the expense of
|
|
// binding.
|
|
|
|
let parent: Node = sourceFile;
|
|
forEachChild(sourceFile, visitNode);
|
|
return;
|
|
|
|
function visitNode(n: Node): void {
|
|
// walk down setting parents that differ from the parent we think it should be. This
|
|
// allows us to quickly bail out of setting parents for subtrees during incremental
|
|
// parsing
|
|
if (n.parent !== parent) {
|
|
n.parent = parent;
|
|
|
|
let saveParent = parent;
|
|
parent = n;
|
|
forEachChild(n, visitNode);
|
|
parent = saveParent;
|
|
}
|
|
}
|
|
}
|
|
|
|
function createSourceFile(fileName: string, languageVersion: ScriptTarget): SourceFile {
|
|
let sourceFile = <SourceFile>createNode(SyntaxKind.SourceFile, /*pos*/ 0);
|
|
|
|
sourceFile.pos = 0;
|
|
sourceFile.end = sourceText.length;
|
|
sourceFile.text = sourceText;
|
|
sourceFile.bindDiagnostics = [];
|
|
sourceFile.languageVersion = languageVersion;
|
|
sourceFile.fileName = normalizePath(fileName);
|
|
sourceFile.flags = fileExtensionIs(sourceFile.fileName, ".d.ts") ? NodeFlags.DeclarationFile : 0;
|
|
|
|
return sourceFile;
|
|
}
|
|
|
|
function setContextFlag(val: Boolean, flag: ParserContextFlags) {
|
|
if (val) {
|
|
contextFlags |= flag;
|
|
}
|
|
else {
|
|
contextFlags &= ~flag;
|
|
}
|
|
}
|
|
|
|
function setStrictModeContext(val: boolean) {
|
|
setContextFlag(val, ParserContextFlags.StrictMode);
|
|
}
|
|
|
|
function setDisallowInContext(val: boolean) {
|
|
setContextFlag(val, ParserContextFlags.DisallowIn);
|
|
}
|
|
|
|
function setYieldContext(val: boolean) {
|
|
setContextFlag(val, ParserContextFlags.Yield);
|
|
}
|
|
|
|
function setGeneratorParameterContext(val: boolean) {
|
|
setContextFlag(val, ParserContextFlags.GeneratorParameter);
|
|
}
|
|
|
|
function setDecoratorContext(val: boolean) {
|
|
setContextFlag(val, ParserContextFlags.Decorator);
|
|
}
|
|
|
|
function doOutsideOfContext<T>(flags: ParserContextFlags, func: () => T): T {
|
|
let currentContextFlags = contextFlags & flags;
|
|
if (currentContextFlags) {
|
|
setContextFlag(false, currentContextFlags);
|
|
let result = func();
|
|
setContextFlag(true, currentContextFlags);
|
|
return result;
|
|
}
|
|
|
|
// no need to do anything special as we are not in any of the requested contexts
|
|
return func();
|
|
}
|
|
|
|
function allowInAnd<T>(func: () => T): T {
|
|
if (contextFlags & ParserContextFlags.DisallowIn) {
|
|
setDisallowInContext(false);
|
|
let result = func();
|
|
setDisallowInContext(true);
|
|
return result;
|
|
}
|
|
|
|
// no need to do anything special if 'in' is already allowed.
|
|
return func();
|
|
}
|
|
|
|
function disallowInAnd<T>(func: () => T): T {
|
|
if (contextFlags & ParserContextFlags.DisallowIn) {
|
|
// no need to do anything special if 'in' is already disallowed.
|
|
return func();
|
|
}
|
|
|
|
setDisallowInContext(true);
|
|
let result = func();
|
|
setDisallowInContext(false);
|
|
return result;
|
|
}
|
|
|
|
function doInYieldContext<T>(func: () => T): T {
|
|
if (contextFlags & ParserContextFlags.Yield) {
|
|
// no need to do anything special if we're already in the [Yield] context.
|
|
return func();
|
|
}
|
|
|
|
setYieldContext(true);
|
|
let result = func();
|
|
setYieldContext(false);
|
|
return result;
|
|
}
|
|
|
|
function doOutsideOfYieldContext<T>(func: () => T): T {
|
|
if (contextFlags & ParserContextFlags.Yield) {
|
|
setYieldContext(false);
|
|
let result = func();
|
|
setYieldContext(true);
|
|
return result;
|
|
}
|
|
|
|
// no need to do anything special if we're not in the [Yield] context.
|
|
return func();
|
|
}
|
|
|
|
function doInDecoratorContext<T>(func: () => T): T {
|
|
if (contextFlags & ParserContextFlags.Decorator) {
|
|
// no need to do anything special if we're already in the [Decorator] context.
|
|
return func();
|
|
}
|
|
|
|
setDecoratorContext(true);
|
|
let result = func();
|
|
setDecoratorContext(false);
|
|
return result;
|
|
}
|
|
|
|
function inYieldContext() {
|
|
return (contextFlags & ParserContextFlags.Yield) !== 0;
|
|
}
|
|
|
|
function inStrictModeContext() {
|
|
return (contextFlags & ParserContextFlags.StrictMode) !== 0;
|
|
}
|
|
|
|
function inGeneratorParameterContext() {
|
|
return (contextFlags & ParserContextFlags.GeneratorParameter) !== 0;
|
|
}
|
|
|
|
function inDisallowInContext() {
|
|
return (contextFlags & ParserContextFlags.DisallowIn) !== 0;
|
|
}
|
|
|
|
function inDecoratorContext() {
|
|
return (contextFlags & ParserContextFlags.Decorator) !== 0;
|
|
}
|
|
|
|
function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): void {
|
|
let start = scanner.getTokenPos();
|
|
let length = scanner.getTextPos() - start;
|
|
|
|
parseErrorAtPosition(start, length, message, arg0);
|
|
}
|
|
|
|
function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): void {
|
|
// Don't report another error if it would just be at the same position as the last error.
|
|
let lastError = lastOrUndefined(parseDiagnostics);
|
|
if (!lastError || start !== lastError.start) {
|
|
parseDiagnostics.push(createFileDiagnostic(sourceFile, start, length, message, arg0));
|
|
}
|
|
|
|
// Mark that we've encountered an error. We'll set an appropriate bit on the next
|
|
// node we finish so that it can't be reused incrementally.
|
|
parseErrorBeforeNextFinishedNode = true;
|
|
}
|
|
|
|
function scanError(message: DiagnosticMessage, length?: number) {
|
|
let pos = scanner.getTextPos();
|
|
parseErrorAtPosition(pos, length || 0, message);
|
|
}
|
|
|
|
function getNodePos(): number {
|
|
return scanner.getStartPos();
|
|
}
|
|
|
|
function getNodeEnd(): number {
|
|
return scanner.getStartPos();
|
|
}
|
|
|
|
function nextToken(): SyntaxKind {
|
|
return token = scanner.scan();
|
|
}
|
|
|
|
function getTokenPos(pos: number): number {
|
|
return skipTrivia(sourceText, pos);
|
|
}
|
|
|
|
function reScanGreaterToken(): SyntaxKind {
|
|
return token = scanner.reScanGreaterToken();
|
|
}
|
|
|
|
function reScanSlashToken(): SyntaxKind {
|
|
return token = scanner.reScanSlashToken();
|
|
}
|
|
|
|
function reScanTemplateToken(): SyntaxKind {
|
|
return token = scanner.reScanTemplateToken();
|
|
}
|
|
|
|
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).
|
|
let saveToken = token;
|
|
let saveParseDiagnosticsLength = parseDiagnostics.length;
|
|
let saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
|
|
|
|
// Note: it is not actually necessary to save/restore the context flags here. That's
|
|
// because the saving/restorating of these flags happens naturally through the recursive
|
|
// descent nature of our parser. However, we still store this here just so we can
|
|
// assert that that invariant holds.
|
|
let saveContextFlags = contextFlags;
|
|
|
|
// If we're only looking ahead, then tell the scanner to only lookahead as well.
|
|
// Otherwise, if we're actually speculatively parsing, then tell the scanner to do the
|
|
// same.
|
|
let result = isLookAhead
|
|
? scanner.lookAhead(callback)
|
|
: scanner.tryScan(callback);
|
|
|
|
Debug.assert(saveContextFlags === contextFlags);
|
|
|
|
// If our callback returned something 'falsy' or we're just looking ahead,
|
|
// then unconditionally restore us to where we were.
|
|
if (!result || isLookAhead) {
|
|
token = saveToken;
|
|
parseDiagnostics.length = saveParseDiagnosticsLength;
|
|
parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Invokes the provided callback then unconditionally restores the parser to the state it
|
|
// was in immediately prior to invoking the callback. The result of invoking the callback
|
|
// is returned from this function.
|
|
function lookAhead<T>(callback: () => T): T {
|
|
return speculationHelper(callback, /*isLookAhead*/ true);
|
|
}
|
|
|
|
// Invokes the provided callback. If the callback returns something falsy, then it restores
|
|
// the parser to the state it was in immediately prior to invoking the callback. If the
|
|
// callback returns something truthy, then the parser state is not rolled back. The result
|
|
// of invoking the callback is returned from this function.
|
|
function tryParse<T>(callback: () => T): T {
|
|
return speculationHelper(callback, /*isLookAhead*/ false);
|
|
}
|
|
|
|
// Ignore strict mode flag because we will report an error in type checker instead.
|
|
function isIdentifier(): boolean {
|
|
if (token === SyntaxKind.Identifier) {
|
|
return true;
|
|
}
|
|
|
|
// If we have a 'yield' keyword, and we're in the [yield] context, then 'yield' is
|
|
// considered a keyword and is not an identifier.
|
|
if (token === SyntaxKind.YieldKeyword && inYieldContext()) {
|
|
return false;
|
|
}
|
|
|
|
return token > SyntaxKind.LastReservedWord;
|
|
}
|
|
|
|
function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage): boolean {
|
|
if (token === kind) {
|
|
nextToken();
|
|
return true;
|
|
}
|
|
|
|
// Report specific message if provided with one. Otherwise, report generic fallback message.
|
|
if (diagnosticMessage) {
|
|
parseErrorAtCurrentToken(diagnosticMessage);
|
|
}
|
|
else {
|
|
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function parseOptional(t: SyntaxKind): boolean {
|
|
if (token === t) {
|
|
nextToken();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function parseOptionalToken(t: SyntaxKind): Node {
|
|
if (token === t) {
|
|
return parseTokenNode();
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function parseExpectedToken(t: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node {
|
|
return parseOptionalToken(t) ||
|
|
createMissingNode(t, reportAtCurrentPosition, diagnosticMessage, arg0);
|
|
}
|
|
|
|
function parseTokenNode<T extends Node>(): T {
|
|
let node = <T>createNode(token);
|
|
nextToken();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function canParseSemicolon() {
|
|
// If there's a real semicolon, then we can always parse it out.
|
|
if (token === SyntaxKind.SemicolonToken) {
|
|
return true;
|
|
}
|
|
|
|
// We can parse out an optional semicolon in ASI cases in the following cases.
|
|
return token === SyntaxKind.CloseBraceToken || token === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak();
|
|
}
|
|
|
|
function parseSemicolon(): boolean {
|
|
if (canParseSemicolon()) {
|
|
if (token === SyntaxKind.SemicolonToken) {
|
|
// consume the semicolon if it was explicitly provided.
|
|
nextToken();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else {
|
|
return parseExpected(SyntaxKind.SemicolonToken);
|
|
}
|
|
}
|
|
|
|
function createNode(kind: SyntaxKind, pos?: number): Node {
|
|
nodeCount++;
|
|
let node = new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))();
|
|
if (!(pos >= 0)) {
|
|
pos = scanner.getStartPos();
|
|
}
|
|
|
|
node.pos = pos;
|
|
node.end = pos;
|
|
return node;
|
|
}
|
|
|
|
function finishNode<T extends Node>(node: T, end?: number): T {
|
|
node.end = end === undefined ? scanner.getStartPos() : end;
|
|
|
|
if (contextFlags) {
|
|
node.parserContextFlags = contextFlags;
|
|
}
|
|
|
|
// Keep track on the node if we encountered an error while parsing it. If we did, then
|
|
// we cannot reuse the node incrementally. Once we've marked this node, clear out the
|
|
// flag so that we don't mark any subsequent nodes.
|
|
if (parseErrorBeforeNextFinishedNode) {
|
|
parseErrorBeforeNextFinishedNode = false;
|
|
node.parserContextFlags |= ParserContextFlags.ThisNodeHasError;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function createMissingNode(kind: SyntaxKind, reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): Node {
|
|
if (reportAtCurrentPosition) {
|
|
parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0);
|
|
}
|
|
else {
|
|
parseErrorAtCurrentToken(diagnosticMessage, arg0);
|
|
}
|
|
|
|
let result = createNode(kind, scanner.getStartPos());
|
|
(<Identifier>result).text = "";
|
|
return finishNode(result);
|
|
}
|
|
|
|
function internIdentifier(text: string): string {
|
|
text = escapeIdentifier(text);
|
|
return hasProperty(identifiers, text) ? identifiers[text] : (identifiers[text] = text);
|
|
}
|
|
|
|
// An identifier that starts with two underscores has an extra underscore character prepended to it to avoid issues
|
|
// with magic property names like '__proto__'. The 'identifiers' object is used to share a single string instance for
|
|
// each identifier in order to reduce memory consumption.
|
|
function createIdentifier(isIdentifier: boolean, diagnosticMessage?: DiagnosticMessage): Identifier {
|
|
identifierCount++;
|
|
if (isIdentifier) {
|
|
let node = <Identifier>createNode(SyntaxKind.Identifier);
|
|
|
|
// Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker
|
|
if (token !== SyntaxKind.Identifier) {
|
|
node.originalKeywordKind = token;
|
|
}
|
|
node.text = internIdentifier(scanner.getTokenValue());
|
|
nextToken();
|
|
return finishNode(node);
|
|
}
|
|
|
|
return <Identifier>createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics.Identifier_expected);
|
|
}
|
|
|
|
function parseIdentifier(diagnosticMessage?: DiagnosticMessage): Identifier {
|
|
return createIdentifier(isIdentifier(), diagnosticMessage);
|
|
}
|
|
|
|
function parseIdentifierName(): Identifier {
|
|
return createIdentifier(isIdentifierOrKeyword());
|
|
}
|
|
|
|
function isLiteralPropertyName(): boolean {
|
|
return isIdentifierOrKeyword() ||
|
|
token === SyntaxKind.StringLiteral ||
|
|
token === SyntaxKind.NumericLiteral;
|
|
}
|
|
|
|
function parsePropertyNameWorker(allowComputedPropertyNames: boolean): DeclarationName {
|
|
if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral) {
|
|
return parseLiteralNode(/*internName*/ true);
|
|
}
|
|
if (allowComputedPropertyNames && token === SyntaxKind.OpenBracketToken) {
|
|
return parseComputedPropertyName();
|
|
}
|
|
return parseIdentifierName();
|
|
}
|
|
|
|
function parsePropertyName(): DeclarationName {
|
|
return parsePropertyNameWorker(/*allowComputedPropertyNames:*/ true);
|
|
}
|
|
|
|
function parseSimplePropertyName(): Identifier | LiteralExpression {
|
|
return <Identifier | LiteralExpression>parsePropertyNameWorker(/*allowComputedPropertyNames:*/ false);
|
|
}
|
|
|
|
function isSimplePropertyName() {
|
|
return token === SyntaxKind.StringLiteral || token === SyntaxKind.NumericLiteral || isIdentifierOrKeyword();
|
|
}
|
|
|
|
function parseComputedPropertyName(): ComputedPropertyName {
|
|
// PropertyName[Yield,GeneratorParameter] :
|
|
// LiteralPropertyName
|
|
// [+GeneratorParameter] ComputedPropertyName
|
|
// [~GeneratorParameter] ComputedPropertyName[?Yield]
|
|
//
|
|
// ComputedPropertyName[Yield] :
|
|
// [ AssignmentExpression[In, ?Yield] ]
|
|
//
|
|
let node = <ComputedPropertyName>createNode(SyntaxKind.ComputedPropertyName);
|
|
parseExpected(SyntaxKind.OpenBracketToken);
|
|
|
|
// We parse any expression (including a comma expression). But the grammar
|
|
// says that only an assignment expression is allowed, so the grammar checker
|
|
// will error if it sees a comma expression.
|
|
let yieldContext = inYieldContext();
|
|
if (inGeneratorParameterContext()) {
|
|
setYieldContext(false);
|
|
}
|
|
|
|
node.expression = allowInAnd(parseExpression);
|
|
if (inGeneratorParameterContext()) {
|
|
setYieldContext(yieldContext);
|
|
}
|
|
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseContextualModifier(t: SyntaxKind): boolean {
|
|
return token === t && tryParse(nextTokenCanFollowModifier);
|
|
}
|
|
|
|
function nextTokenCanFollowModifier() {
|
|
if (token === SyntaxKind.ConstKeyword) {
|
|
// 'const' is only a modifier if followed by 'enum'.
|
|
return nextToken() === SyntaxKind.EnumKeyword;
|
|
}
|
|
if (token === SyntaxKind.ExportKeyword) {
|
|
nextToken();
|
|
if (token === SyntaxKind.DefaultKeyword) {
|
|
return lookAhead(nextTokenIsClassOrFunction);
|
|
}
|
|
return token !== SyntaxKind.AsteriskToken && token !== SyntaxKind.OpenBraceToken && canFollowModifier();
|
|
}
|
|
if (token === SyntaxKind.DefaultKeyword) {
|
|
return nextTokenIsClassOrFunction();
|
|
}
|
|
nextToken();
|
|
return canFollowModifier();
|
|
}
|
|
|
|
function parseAnyContextualModifier(): boolean {
|
|
return isModifier(token) && tryParse(nextTokenCanFollowModifier);
|
|
}
|
|
|
|
function canFollowModifier(): boolean {
|
|
return token === SyntaxKind.OpenBracketToken
|
|
|| token === SyntaxKind.OpenBraceToken
|
|
|| token === SyntaxKind.AsteriskToken
|
|
|| isLiteralPropertyName();
|
|
}
|
|
|
|
function nextTokenIsClassOrFunction(): boolean {
|
|
nextToken();
|
|
return token === SyntaxKind.ClassKeyword || token === SyntaxKind.FunctionKeyword;
|
|
}
|
|
|
|
// True if positioned at the start of a list element
|
|
function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean {
|
|
let node = currentNode(parsingContext);
|
|
if (node) {
|
|
return true;
|
|
}
|
|
|
|
switch (parsingContext) {
|
|
case ParsingContext.SourceElements:
|
|
case ParsingContext.ModuleElements:
|
|
// If we're in error recovery, then we don't want to treat ';' as an empty statement.
|
|
// The problem is that ';' can show up in far too many contexts, and if we see one
|
|
// and assume it's a statement, then we may bail out inappropriately from whatever
|
|
// we're parsing. For example, if we have a semicolon in the middle of a class, then
|
|
// we really don't want to assume the class is over and we're on a statement in the
|
|
// outer module. We just want to consume and move on.
|
|
return !(token === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfModuleElement();
|
|
case ParsingContext.BlockStatements:
|
|
case ParsingContext.SwitchClauseStatements:
|
|
// During error recovery we don't treat empty statements as statements
|
|
return !(token === SyntaxKind.SemicolonToken && inErrorRecovery) && isStartOfStatement();
|
|
case ParsingContext.SwitchClauses:
|
|
return token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword;
|
|
case ParsingContext.TypeMembers:
|
|
return isStartOfTypeMember();
|
|
case ParsingContext.ClassMembers:
|
|
// We allow semicolons as class elements (as specified by ES6) as long as we're
|
|
// not in error recovery. If we're in error recovery, we don't want an errant
|
|
// semicolon to be treated as a class member (since they're almost always used
|
|
// for statements.
|
|
return lookAhead(isClassMemberStart) || (token === SyntaxKind.SemicolonToken && !inErrorRecovery);
|
|
case ParsingContext.EnumMembers:
|
|
// Include open bracket computed properties. This technically also lets in indexers,
|
|
// which would be a candidate for improved error reporting.
|
|
return token === SyntaxKind.OpenBracketToken || isLiteralPropertyName();
|
|
case ParsingContext.ObjectLiteralMembers:
|
|
return token === SyntaxKind.OpenBracketToken || token === SyntaxKind.AsteriskToken || isLiteralPropertyName();
|
|
case ParsingContext.ObjectBindingElements:
|
|
return isLiteralPropertyName();
|
|
case ParsingContext.HeritageClauseElement:
|
|
// If we see { } then only consume it as an expression if it is followed by , or {
|
|
// That way we won't consume the body of a class in its heritage clause.
|
|
if (token === SyntaxKind.OpenBraceToken) {
|
|
return lookAhead(isValidHeritageClauseObjectLiteral);
|
|
}
|
|
|
|
if (!inErrorRecovery) {
|
|
return isStartOfLeftHandSideExpression() && !isHeritageClauseExtendsOrImplementsKeyword();
|
|
}
|
|
else {
|
|
// If we're in error recovery we tighten up what we're willing to match.
|
|
// That way we don't treat something like "this" as a valid heritage clause
|
|
// element during recovery.
|
|
return isIdentifier() && !isHeritageClauseExtendsOrImplementsKeyword();
|
|
}
|
|
case ParsingContext.VariableDeclarations:
|
|
return isIdentifierOrPattern();
|
|
case ParsingContext.ArrayBindingElements:
|
|
return token === SyntaxKind.CommaToken || token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern();
|
|
case ParsingContext.TypeParameters:
|
|
return isIdentifier();
|
|
case ParsingContext.ArgumentExpressions:
|
|
case ParsingContext.ArrayLiteralMembers:
|
|
return token === SyntaxKind.CommaToken || token === SyntaxKind.DotDotDotToken || isStartOfExpression();
|
|
case ParsingContext.Parameters:
|
|
return isStartOfParameter();
|
|
case ParsingContext.TypeArguments:
|
|
case ParsingContext.TupleElementTypes:
|
|
return token === SyntaxKind.CommaToken || isStartOfType();
|
|
case ParsingContext.HeritageClauses:
|
|
return isHeritageClause();
|
|
case ParsingContext.ImportOrExportSpecifiers:
|
|
return isIdentifierOrKeyword();
|
|
case ParsingContext.JSDocFunctionParameters:
|
|
case ParsingContext.JSDocTypeArguments:
|
|
case ParsingContext.JSDocTupleTypes:
|
|
return JSDocParser.isJSDocType();
|
|
case ParsingContext.JSDocRecordMembers:
|
|
return isSimplePropertyName();
|
|
}
|
|
|
|
Debug.fail("Non-exhaustive case in 'isListElement'.");
|
|
}
|
|
|
|
function isValidHeritageClauseObjectLiteral() {
|
|
Debug.assert(token === SyntaxKind.OpenBraceToken);
|
|
if (nextToken() === SyntaxKind.CloseBraceToken) {
|
|
// if we see "extends {}" then only treat the {} as what we're extending (and not
|
|
// the class body) if we have:
|
|
//
|
|
// extends {} {
|
|
// extends {},
|
|
// extends {} extends
|
|
// extends {} implements
|
|
|
|
let next = nextToken();
|
|
return next === SyntaxKind.CommaToken || next === SyntaxKind.OpenBraceToken || next === SyntaxKind.ExtendsKeyword || next === SyntaxKind.ImplementsKeyword;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function nextTokenIsIdentifier() {
|
|
nextToken();
|
|
return isIdentifier();
|
|
}
|
|
|
|
function isHeritageClauseExtendsOrImplementsKeyword(): boolean {
|
|
if (token === SyntaxKind.ImplementsKeyword ||
|
|
token === SyntaxKind.ExtendsKeyword) {
|
|
|
|
return lookAhead(nextTokenIsStartOfExpression);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function nextTokenIsStartOfExpression() {
|
|
nextToken();
|
|
return isStartOfExpression();
|
|
}
|
|
|
|
// True if positioned at a list terminator
|
|
function isListTerminator(kind: ParsingContext): boolean {
|
|
if (token === SyntaxKind.EndOfFileToken) {
|
|
// Being at the end of the file ends all lists.
|
|
return true;
|
|
}
|
|
|
|
switch (kind) {
|
|
case ParsingContext.ModuleElements:
|
|
case ParsingContext.BlockStatements:
|
|
case ParsingContext.SwitchClauses:
|
|
case ParsingContext.TypeMembers:
|
|
case ParsingContext.ClassMembers:
|
|
case ParsingContext.EnumMembers:
|
|
case ParsingContext.ObjectLiteralMembers:
|
|
case ParsingContext.ObjectBindingElements:
|
|
case ParsingContext.ImportOrExportSpecifiers:
|
|
return token === SyntaxKind.CloseBraceToken;
|
|
case ParsingContext.SwitchClauseStatements:
|
|
return token === SyntaxKind.CloseBraceToken || token === SyntaxKind.CaseKeyword || token === SyntaxKind.DefaultKeyword;
|
|
case ParsingContext.HeritageClauseElement:
|
|
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword;
|
|
case ParsingContext.VariableDeclarations:
|
|
return isVariableDeclaratorListTerminator();
|
|
case ParsingContext.TypeParameters:
|
|
// Tokens other than '>' are here for better error recovery
|
|
return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.OpenBraceToken || token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword;
|
|
case ParsingContext.ArgumentExpressions:
|
|
// Tokens other than ')' are here for better error recovery
|
|
return token === SyntaxKind.CloseParenToken || token === SyntaxKind.SemicolonToken;
|
|
case ParsingContext.ArrayLiteralMembers:
|
|
case ParsingContext.TupleElementTypes:
|
|
case ParsingContext.ArrayBindingElements:
|
|
return token === SyntaxKind.CloseBracketToken;
|
|
case ParsingContext.Parameters:
|
|
// Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery
|
|
return token === SyntaxKind.CloseParenToken || token === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/;
|
|
case ParsingContext.TypeArguments:
|
|
// Tokens other than '>' are here for better error recovery
|
|
return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken;
|
|
case ParsingContext.HeritageClauses:
|
|
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.CloseBraceToken;
|
|
case ParsingContext.JSDocFunctionParameters:
|
|
return token === SyntaxKind.CloseParenToken || token === SyntaxKind.ColonToken || token === SyntaxKind.CloseBraceToken;
|
|
case ParsingContext.JSDocTypeArguments:
|
|
return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.CloseBraceToken;
|
|
case ParsingContext.JSDocTupleTypes:
|
|
return token === SyntaxKind.CloseBracketToken || token === SyntaxKind.CloseBraceToken;
|
|
case ParsingContext.JSDocRecordMembers:
|
|
return token === SyntaxKind.CloseBraceToken;
|
|
}
|
|
}
|
|
|
|
function isVariableDeclaratorListTerminator(): boolean {
|
|
// If we can consume a semicolon (either explicitly, or with ASI), then consider us done
|
|
// with parsing the list of variable declarators.
|
|
if (canParseSemicolon()) {
|
|
return true;
|
|
}
|
|
|
|
// in the case where we're parsing the variable declarator of a 'for-in' statement, we
|
|
// are done if we see an 'in' keyword in front of us. Same with for-of
|
|
if (isInOrOfKeyword(token)) {
|
|
return true;
|
|
}
|
|
|
|
// ERROR RECOVERY TWEAK:
|
|
// For better error recovery, if we see an '=>' then we just stop immediately. We've got an
|
|
// arrow function here and it's going to be very unlikely that we'll resynchronize and get
|
|
// another variable declaration.
|
|
if (token === SyntaxKind.EqualsGreaterThanToken) {
|
|
return true;
|
|
}
|
|
|
|
// Keep trying to parse out variable declarators.
|
|
return false;
|
|
}
|
|
|
|
// True if positioned at element or terminator of the current list or any enclosing list
|
|
function isInSomeParsingContext(): boolean {
|
|
for (let kind = 0; kind < ParsingContext.Count; kind++) {
|
|
if (parsingContext & (1 << kind)) {
|
|
if (isListElement(kind, /* inErrorRecovery */ true) || isListTerminator(kind)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Parses a list of elements
|
|
function parseList<T extends Node>(kind: ParsingContext, checkForStrictMode: boolean, parseElement: () => T): NodeArray<T> {
|
|
let saveParsingContext = parsingContext;
|
|
parsingContext |= 1 << kind;
|
|
let result = <NodeArray<T>>[];
|
|
result.pos = getNodePos();
|
|
let savedStrictModeContext = inStrictModeContext();
|
|
|
|
while (!isListTerminator(kind)) {
|
|
if (isListElement(kind, /* inErrorRecovery */ false)) {
|
|
let element = parseListElement(kind, parseElement);
|
|
result.push(element);
|
|
|
|
// test elements only if we are not already in strict mode
|
|
if (checkForStrictMode && !inStrictModeContext()) {
|
|
if (isPrologueDirective(element)) {
|
|
if (isUseStrictPrologueDirective(element)) {
|
|
setStrictModeContext(true);
|
|
checkForStrictMode = false;
|
|
}
|
|
}
|
|
else {
|
|
checkForStrictMode = false;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (abortParsingListOrMoveToNextToken(kind)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
setStrictModeContext(savedStrictModeContext);
|
|
result.end = getNodeEnd();
|
|
parsingContext = saveParsingContext;
|
|
return result;
|
|
}
|
|
|
|
/// Should be called only on prologue directives (isPrologueDirective(node) should be true)
|
|
function isUseStrictPrologueDirective(node: Node): boolean {
|
|
Debug.assert(isPrologueDirective(node));
|
|
let nodeText = getTextOfNodeFromSourceText(sourceText, (<ExpressionStatement>node).expression);
|
|
|
|
// Note: the node text must be exactly "use strict" or 'use strict'. It is not ok for the
|
|
// string to contain unicode escapes (as per ES5).
|
|
return nodeText === '"use strict"' || nodeText === "'use strict'";
|
|
}
|
|
|
|
function parseListElement<T extends Node>(parsingContext: ParsingContext, parseElement: () => T): T {
|
|
let node = currentNode(parsingContext);
|
|
if (node) {
|
|
return <T>consumeNode(node);
|
|
}
|
|
|
|
return parseElement();
|
|
}
|
|
|
|
function currentNode(parsingContext: ParsingContext): Node {
|
|
// If there is an outstanding parse error that we've encountered, but not attached to
|
|
// some node, then we cannot get a node from the old source tree. This is because we
|
|
// want to mark the next node we encounter as being unusable.
|
|
//
|
|
// Note: This may be too conservative. Perhaps we could reuse the node and set the bit
|
|
// on it (or its leftmost child) as having the error. For now though, being conservative
|
|
// is nice and likely won't ever affect perf.
|
|
if (parseErrorBeforeNextFinishedNode) {
|
|
return undefined;
|
|
}
|
|
|
|
if (!syntaxCursor) {
|
|
// if we don't have a cursor, we could never return a node from the old tree.
|
|
return undefined;
|
|
}
|
|
|
|
let node = syntaxCursor.currentNode(scanner.getStartPos());
|
|
|
|
// Can't reuse a missing node.
|
|
if (nodeIsMissing(node)) {
|
|
return undefined;
|
|
}
|
|
|
|
// Can't reuse a node that intersected the change range.
|
|
if (node.intersectsChange) {
|
|
return undefined;
|
|
}
|
|
|
|
// Can't reuse a node that contains a parse error. This is necessary so that we
|
|
// produce the same set of errors again.
|
|
if (containsParseError(node)) {
|
|
return undefined;
|
|
}
|
|
|
|
// We can only reuse a node if it was parsed under the same strict mode that we're
|
|
// currently in. i.e. if we originally parsed a node in non-strict mode, but then
|
|
// the user added 'using strict' at the top of the file, then we can't use that node
|
|
// again as the presense of strict mode may cause us to parse the tokens in the file
|
|
// differetly.
|
|
//
|
|
// Note: we *can* reuse tokens when the strict mode changes. That's because tokens
|
|
// are unaffected by strict mode. It's just the parser will decide what to do with it
|
|
// differently depending on what mode it is in.
|
|
//
|
|
// This also applies to all our other context flags as well.
|
|
let nodeContextFlags = node.parserContextFlags & ParserContextFlags.ParserGeneratedFlags;
|
|
if (nodeContextFlags !== contextFlags) {
|
|
return undefined;
|
|
}
|
|
|
|
// Ok, we have a node that looks like it could be reused. Now verify that it is valid
|
|
// in the currest list parsing context that we're currently at.
|
|
if (!canReuseNode(node, parsingContext)) {
|
|
return undefined;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function consumeNode(node: Node) {
|
|
// Move the scanner so it is after the node we just consumed.
|
|
scanner.setTextPos(node.end);
|
|
nextToken();
|
|
return node;
|
|
}
|
|
|
|
function canReuseNode(node: Node, parsingContext: ParsingContext): boolean {
|
|
switch (parsingContext) {
|
|
case ParsingContext.ModuleElements:
|
|
return isReusableModuleElement(node);
|
|
|
|
case ParsingContext.ClassMembers:
|
|
return isReusableClassMember(node);
|
|
|
|
case ParsingContext.SwitchClauses:
|
|
return isReusableSwitchClause(node);
|
|
|
|
case ParsingContext.BlockStatements:
|
|
case ParsingContext.SwitchClauseStatements:
|
|
return isReusableStatement(node);
|
|
|
|
case ParsingContext.EnumMembers:
|
|
return isReusableEnumMember(node);
|
|
|
|
case ParsingContext.TypeMembers:
|
|
return isReusableTypeMember(node);
|
|
|
|
case ParsingContext.VariableDeclarations:
|
|
return isReusableVariableDeclaration(node);
|
|
|
|
case ParsingContext.Parameters:
|
|
return isReusableParameter(node);
|
|
|
|
// Any other lists we do not care about reusing nodes in. But feel free to add if
|
|
// you can do so safely. Danger areas involve nodes that may involve speculative
|
|
// parsing. If speculative parsing is involved with the node, then the range the
|
|
// parser reached while looking ahead might be in the edited range (see the example
|
|
// in canReuseVariableDeclaratorNode for a good case of this).
|
|
case ParsingContext.HeritageClauses:
|
|
// This would probably be safe to reuse. There is no speculative parsing with
|
|
// heritage clauses.
|
|
|
|
case ParsingContext.TypeParameters:
|
|
// This would probably be safe to reuse. There is no speculative parsing with
|
|
// type parameters. Note that that's because type *parameters* only occur in
|
|
// unambiguous *type* contexts. While type *arguments* occur in very ambiguous
|
|
// *expression* contexts.
|
|
|
|
case ParsingContext.TupleElementTypes:
|
|
// This would probably be safe to reuse. There is no speculative parsing with
|
|
// tuple types.
|
|
|
|
// Technically, type argument list types are probably safe to reuse. While
|
|
// speculative parsing is involved with them (since type argument lists are only
|
|
// produced from speculative parsing a < as a type argument list), we only have
|
|
// the types because speculative parsing succeeded. Thus, the lookahead never
|
|
// went past the end of the list and rewound.
|
|
case ParsingContext.TypeArguments:
|
|
|
|
// Note: these are almost certainly not safe to ever reuse. Expressions commonly
|
|
// need a large amount of lookahead, and we should not reuse them as they may
|
|
// have actually intersected the edit.
|
|
case ParsingContext.ArgumentExpressions:
|
|
|
|
// This is not safe to reuse for the same reason as the 'AssignmentExpression'
|
|
// cases. i.e. a property assignment may end with an expression, and thus might
|
|
// have lookahead far beyond it's old node.
|
|
case ParsingContext.ObjectLiteralMembers:
|
|
|
|
// This is probably not safe to reuse. There can be speculative parsing with
|
|
// type names in a heritage clause. There can be generic names in the type
|
|
// name list, and there can be left hand side expressions (which can have type
|
|
// arguments.)
|
|
case ParsingContext.HeritageClauseElement:
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isReusableModuleElement(node: Node) {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ImportDeclaration:
|
|
case SyntaxKind.ImportEqualsDeclaration:
|
|
case SyntaxKind.ExportDeclaration:
|
|
case SyntaxKind.ExportAssignment:
|
|
case SyntaxKind.ClassDeclaration:
|
|
case SyntaxKind.InterfaceDeclaration:
|
|
case SyntaxKind.ModuleDeclaration:
|
|
case SyntaxKind.EnumDeclaration:
|
|
return true;
|
|
}
|
|
|
|
return isReusableStatement(node);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isReusableClassMember(node: Node) {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.Constructor:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.MethodDeclaration:
|
|
case SyntaxKind.GetAccessor:
|
|
case SyntaxKind.SetAccessor:
|
|
case SyntaxKind.PropertyDeclaration:
|
|
case SyntaxKind.SemicolonClassElement:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isReusableSwitchClause(node: Node) {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.CaseClause:
|
|
case SyntaxKind.DefaultClause:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isReusableStatement(node: Node) {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.FunctionDeclaration:
|
|
case SyntaxKind.VariableStatement:
|
|
case SyntaxKind.Block:
|
|
case SyntaxKind.IfStatement:
|
|
case SyntaxKind.ExpressionStatement:
|
|
case SyntaxKind.ThrowStatement:
|
|
case SyntaxKind.ReturnStatement:
|
|
case SyntaxKind.SwitchStatement:
|
|
case SyntaxKind.BreakStatement:
|
|
case SyntaxKind.ContinueStatement:
|
|
case SyntaxKind.ForInStatement:
|
|
case SyntaxKind.ForOfStatement:
|
|
case SyntaxKind.ForStatement:
|
|
case SyntaxKind.WhileStatement:
|
|
case SyntaxKind.WithStatement:
|
|
case SyntaxKind.EmptyStatement:
|
|
case SyntaxKind.TryStatement:
|
|
case SyntaxKind.LabeledStatement:
|
|
case SyntaxKind.DoStatement:
|
|
case SyntaxKind.DebuggerStatement:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isReusableEnumMember(node: Node) {
|
|
return node.kind === SyntaxKind.EnumMember;
|
|
}
|
|
|
|
function isReusableTypeMember(node: Node) {
|
|
if (node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.ConstructSignature:
|
|
case SyntaxKind.MethodSignature:
|
|
case SyntaxKind.IndexSignature:
|
|
case SyntaxKind.PropertySignature:
|
|
case SyntaxKind.CallSignature:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function isReusableVariableDeclaration(node: Node) {
|
|
if (node.kind !== SyntaxKind.VariableDeclaration) {
|
|
return false;
|
|
}
|
|
|
|
// Very subtle incremental parsing bug. Consider the following code:
|
|
//
|
|
// let v = new List < A, B
|
|
//
|
|
// This is actually legal code. It's a list of variable declarators "v = new List<A"
|
|
// on one side and "B" on the other. If you then change that to:
|
|
//
|
|
// let v = new List < A, B >()
|
|
//
|
|
// then we have a problem. "v = new List<A" doesn't intersect the change range, so we
|
|
// start reparsing at "B" and we completely fail to handle this properly.
|
|
//
|
|
// In order to prevent this, we do not allow a variable declarator to be reused if it
|
|
// has an initializer.
|
|
let variableDeclarator = <VariableDeclaration>node;
|
|
return variableDeclarator.initializer === undefined;
|
|
}
|
|
|
|
function isReusableParameter(node: Node) {
|
|
if (node.kind !== SyntaxKind.Parameter) {
|
|
return false;
|
|
}
|
|
|
|
// See the comment in isReusableVariableDeclaration for why we do this.
|
|
let parameter = <ParameterDeclaration>node;
|
|
return parameter.initializer === undefined;
|
|
}
|
|
|
|
// Returns true if we should abort parsing.
|
|
function abortParsingListOrMoveToNextToken(kind: ParsingContext) {
|
|
parseErrorAtCurrentToken(parsingContextErrors(kind));
|
|
if (isInSomeParsingContext()) {
|
|
return true;
|
|
}
|
|
|
|
nextToken();
|
|
return false;
|
|
}
|
|
|
|
function parsingContextErrors(context: ParsingContext): DiagnosticMessage {
|
|
switch (context) {
|
|
case ParsingContext.SourceElements: return Diagnostics.Declaration_or_statement_expected;
|
|
case ParsingContext.ModuleElements: return Diagnostics.Declaration_or_statement_expected;
|
|
case ParsingContext.BlockStatements: return Diagnostics.Statement_expected;
|
|
case ParsingContext.SwitchClauses: return Diagnostics.case_or_default_expected;
|
|
case ParsingContext.SwitchClauseStatements: return Diagnostics.Statement_expected;
|
|
case ParsingContext.TypeMembers: return Diagnostics.Property_or_signature_expected;
|
|
case ParsingContext.ClassMembers: return Diagnostics.Unexpected_token_A_constructor_method_accessor_or_property_was_expected;
|
|
case ParsingContext.EnumMembers: return Diagnostics.Enum_member_expected;
|
|
case ParsingContext.HeritageClauseElement: return Diagnostics.Expression_expected;
|
|
case ParsingContext.VariableDeclarations: return Diagnostics.Variable_declaration_expected;
|
|
case ParsingContext.ObjectBindingElements: return Diagnostics.Property_destructuring_pattern_expected;
|
|
case ParsingContext.ArrayBindingElements: return Diagnostics.Array_element_destructuring_pattern_expected;
|
|
case ParsingContext.ArgumentExpressions: return Diagnostics.Argument_expression_expected;
|
|
case ParsingContext.ObjectLiteralMembers: return Diagnostics.Property_assignment_expected;
|
|
case ParsingContext.ArrayLiteralMembers: return Diagnostics.Expression_or_comma_expected;
|
|
case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected;
|
|
case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected;
|
|
case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected;
|
|
case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected;
|
|
case ParsingContext.HeritageClauses: return Diagnostics.Unexpected_token_expected;
|
|
case ParsingContext.ImportOrExportSpecifiers: 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;
|
|
case ParsingContext.JSDocRecordMembers: return Diagnostics.Property_assignment_expected;
|
|
}
|
|
};
|
|
|
|
// Parses a comma-delimited list of elements
|
|
function parseDelimitedList<T extends Node>(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimeter?: boolean): NodeArray<T> {
|
|
let saveParsingContext = parsingContext;
|
|
parsingContext |= 1 << kind;
|
|
let result = <NodeArray<T>>[];
|
|
result.pos = getNodePos();
|
|
|
|
let commaStart = -1; // Meaning the previous token was not a comma
|
|
while (true) {
|
|
if (isListElement(kind, /* inErrorRecovery */ false)) {
|
|
result.push(parseListElement(kind, parseElement));
|
|
commaStart = scanner.getTokenPos();
|
|
if (parseOptional(SyntaxKind.CommaToken)) {
|
|
continue;
|
|
}
|
|
|
|
commaStart = -1; // Back to the state where the last token was not a comma
|
|
if (isListTerminator(kind)) {
|
|
break;
|
|
}
|
|
|
|
// We didn't get a comma, and the list wasn't terminated, explicitly parse
|
|
// out a comma so we give a good error message.
|
|
parseExpected(SyntaxKind.CommaToken);
|
|
|
|
// If the token was a semicolon, and the caller allows that, then skip it and
|
|
// continue. This ensures we get back on track and don't result in tons of
|
|
// parse errors. For example, this can happen when people do things like use
|
|
// a semicolon to delimit object literal members. Note: we'll have already
|
|
// reported an error when we called parseExpected above.
|
|
if (considerSemicolonAsDelimeter && token === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) {
|
|
nextToken();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (isListTerminator(kind)) {
|
|
break;
|
|
}
|
|
|
|
if (abortParsingListOrMoveToNextToken(kind)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Recording the trailing comma is deliberately done after the previous
|
|
// loop, and not just if we see a list terminator. This is because the list
|
|
// may have ended incorrectly, but it is still important to know if there
|
|
// was a trailing comma.
|
|
// Check if the last token was a comma.
|
|
if (commaStart >= 0) {
|
|
// Always preserve a trailing comma by marking it on the NodeArray
|
|
result.hasTrailingComma = true;
|
|
}
|
|
|
|
result.end = getNodeEnd();
|
|
parsingContext = saveParsingContext;
|
|
return result;
|
|
}
|
|
|
|
function createMissingList<T>(): NodeArray<T> {
|
|
let pos = getNodePos();
|
|
let result = <NodeArray<T>>[];
|
|
result.pos = pos;
|
|
result.end = pos;
|
|
return result;
|
|
}
|
|
|
|
function parseBracketedList<T extends Node>(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray<T> {
|
|
if (parseExpected(open)) {
|
|
let result = parseDelimitedList(kind, parseElement);
|
|
parseExpected(close);
|
|
return result;
|
|
}
|
|
|
|
return createMissingList<T>();
|
|
}
|
|
|
|
// The allowReservedWords parameter controls whether reserved words are permitted after the first dot
|
|
function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName {
|
|
let entity: EntityName = parseIdentifier(diagnosticMessage);
|
|
while (parseOptional(SyntaxKind.DotToken)) {
|
|
let node = <QualifiedName>createNode(SyntaxKind.QualifiedName, entity.pos);
|
|
node.left = entity;
|
|
node.right = parseRightSideOfDot(allowReservedWords);
|
|
entity = finishNode(node);
|
|
}
|
|
return entity;
|
|
}
|
|
|
|
function parseRightSideOfDot(allowIdentifierNames: boolean): Identifier {
|
|
// Technically a keyword is valid here as all keywords are identifier names.
|
|
// However, often we'll encounter this in error situations when the keyword
|
|
// is actually starting another valid construct.
|
|
//
|
|
// So, we check for the following specific case:
|
|
//
|
|
// name.
|
|
// keyword identifierNameOrKeyword
|
|
//
|
|
// Note: the newlines are important here. For example, if that above code
|
|
// were rewritten into:
|
|
//
|
|
// name.keyword
|
|
// identifierNameOrKeyword
|
|
//
|
|
// Then we would consider it valid. That's because ASI would take effect and
|
|
// the code would be implicitly: "name.keyword; identifierNameOrKeyword".
|
|
// In the first case though, ASI will not take effect because there is not a
|
|
// line terminator after the keyword.
|
|
if (scanner.hasPrecedingLineBreak() && scanner.isReservedWord()) {
|
|
let matchesPattern = lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine);
|
|
|
|
if (matchesPattern) {
|
|
// Report that we need an identifier. However, report it right after the dot,
|
|
// and not on the next token. This is because the next token might actually
|
|
// be an identifier and the error woudl be quite confusing.
|
|
return <Identifier>createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentToken*/ true, Diagnostics.Identifier_expected);
|
|
}
|
|
}
|
|
|
|
return allowIdentifierNames ? parseIdentifierName() : parseIdentifier();
|
|
}
|
|
|
|
function parseTemplateExpression(): TemplateExpression {
|
|
let template = <TemplateExpression>createNode(SyntaxKind.TemplateExpression);
|
|
|
|
template.head = parseLiteralNode();
|
|
Debug.assert(template.head.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind");
|
|
|
|
let templateSpans = <NodeArray<TemplateSpan>>[];
|
|
templateSpans.pos = getNodePos();
|
|
|
|
do {
|
|
templateSpans.push(parseTemplateSpan());
|
|
}
|
|
while (lastOrUndefined(templateSpans).literal.kind === SyntaxKind.TemplateMiddle)
|
|
|
|
templateSpans.end = getNodeEnd();
|
|
template.templateSpans = templateSpans;
|
|
|
|
return finishNode(template);
|
|
}
|
|
|
|
function parseTemplateSpan(): TemplateSpan {
|
|
let span = <TemplateSpan>createNode(SyntaxKind.TemplateSpan);
|
|
span.expression = allowInAnd(parseExpression);
|
|
|
|
let literal: LiteralExpression;
|
|
|
|
if (token === SyntaxKind.CloseBraceToken) {
|
|
reScanTemplateToken()
|
|
literal = parseLiteralNode();
|
|
}
|
|
else {
|
|
literal = <LiteralExpression>parseExpectedToken(SyntaxKind.TemplateTail, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken));
|
|
}
|
|
|
|
span.literal = literal;
|
|
return finishNode(span);
|
|
}
|
|
|
|
function parseLiteralNode(internName?: boolean): LiteralExpression {
|
|
let node = <LiteralExpression>createNode(token);
|
|
let text = scanner.getTokenValue();
|
|
node.text = internName ? internIdentifier(text) : text;
|
|
|
|
if (scanner.hasExtendedUnicodeEscape()) {
|
|
node.hasExtendedUnicodeEscape = true;
|
|
}
|
|
|
|
if (scanner.isUnterminated()) {
|
|
node.isUnterminated = true;
|
|
}
|
|
|
|
let tokenPos = scanner.getTokenPos();
|
|
nextToken();
|
|
finishNode(node);
|
|
|
|
// Octal literals are not allowed in strict mode or ES5
|
|
// Note that theoretically the following condition would hold true literals like 009,
|
|
// which is not octal.But because of how the scanner separates the tokens, we would
|
|
// never get a token like this. Instead, we would get 00 and 9 as two separate tokens.
|
|
// We also do not need to check for negatives because any prefix operator would be part of a
|
|
// parent unary expression.
|
|
if (node.kind === SyntaxKind.NumericLiteral
|
|
&& sourceText.charCodeAt(tokenPos) === CharacterCodes._0
|
|
&& isOctalDigit(sourceText.charCodeAt(tokenPos + 1))) {
|
|
|
|
node.flags |= NodeFlags.OctalLiteral;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
// TYPES
|
|
|
|
function parseTypeReference(): TypeReferenceNode {
|
|
let node = <TypeReferenceNode>createNode(SyntaxKind.TypeReference);
|
|
node.typeName = parseEntityName(/*allowReservedWords*/ false, Diagnostics.Type_expected);
|
|
if (!scanner.hasPrecedingLineBreak() && token === SyntaxKind.LessThanToken) {
|
|
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseTypeQuery(): TypeQueryNode {
|
|
let node = <TypeQueryNode>createNode(SyntaxKind.TypeQuery);
|
|
parseExpected(SyntaxKind.TypeOfKeyword);
|
|
node.exprName = parseEntityName(/*allowReservedWords*/ true);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseTypeParameter(): TypeParameterDeclaration {
|
|
let node = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
|
|
node.name = parseIdentifier();
|
|
if (parseOptional(SyntaxKind.ExtendsKeyword)) {
|
|
// It's not uncommon for people to write improper constraints to a generic. If the
|
|
// user writes a constraint that is an expression and not an actual type, then parse
|
|
// it out as an expression (so we can recover well), but report that a type is needed
|
|
// instead.
|
|
if (isStartOfType() || !isStartOfExpression()) {
|
|
node.constraint = parseType();
|
|
}
|
|
else {
|
|
// It was not a type, and it looked like an expression. Parse out an expression
|
|
// here so we recover well. Note: it is important that we call parseUnaryExpression
|
|
// and not parseExpression here. If the user has:
|
|
//
|
|
// <T extends "">
|
|
//
|
|
// We do *not* want to consume the > as we're consuming the expression for "".
|
|
node.expression = parseUnaryExpressionOrHigher();
|
|
}
|
|
}
|
|
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseTypeParameters(): NodeArray<TypeParameterDeclaration> {
|
|
if (token === SyntaxKind.LessThanToken) {
|
|
return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
|
|
}
|
|
}
|
|
|
|
function parseParameterType(): TypeNode {
|
|
if (parseOptional(SyntaxKind.ColonToken)) {
|
|
return token === SyntaxKind.StringLiteral
|
|
? <StringLiteral>parseLiteralNode(/*internName*/ true)
|
|
: parseType();
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function isStartOfParameter(): boolean {
|
|
return token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifier(token) || token === SyntaxKind.AtToken;
|
|
}
|
|
|
|
function setModifiers(node: Node, modifiers: ModifiersArray) {
|
|
if (modifiers) {
|
|
node.flags |= modifiers.flags;
|
|
node.modifiers = modifiers;
|
|
}
|
|
}
|
|
|
|
function parseParameter(): ParameterDeclaration {
|
|
let node = <ParameterDeclaration>createNode(SyntaxKind.Parameter);
|
|
node.decorators = parseDecorators();
|
|
setModifiers(node, parseModifiers());
|
|
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
|
|
|
|
// SingleNameBinding[Yield,GeneratorParameter] : See 13.2.3
|
|
// [+GeneratorParameter]BindingIdentifier[Yield]Initializer[In]opt
|
|
// [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt
|
|
|
|
node.name = inGeneratorParameterContext() ? doInYieldContext(parseIdentifierOrPattern) : parseIdentifierOrPattern();
|
|
|
|
if (getFullWidth(node.name) === 0 && node.flags === 0 && isModifier(token)) {
|
|
// in cases like
|
|
// 'use strict'
|
|
// function foo(static)
|
|
// isParameter('static') === true, because of isModifier('static')
|
|
// however 'static' is not a legal identifier in a strict mode.
|
|
// so result of this function will be ParameterDeclaration (flags = 0, name = missing, type = undefined, initializer = undefined)
|
|
// and current token will not change => parsing of the enclosing parameter list will last till the end of time (or OOM)
|
|
// to avoid this we'll advance cursor to the next token.
|
|
nextToken();
|
|
}
|
|
|
|
node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
|
|
node.type = parseParameterType();
|
|
node.initializer = inGeneratorParameterContext() ? doOutsideOfYieldContext(parseParameterInitializer) : parseParameterInitializer();
|
|
|
|
// Do not check for initializers in an ambient context for parameters. This is not
|
|
// a grammar error because the grammar allows arbitrary call signatures in
|
|
// an ambient context.
|
|
// It is actually not necessary for this to be an error at all. The reason is that
|
|
// function/constructor implementations are syntactically disallowed in ambient
|
|
// contexts. In addition, parameter initializers are semantically disallowed in
|
|
// overload signatures. So parameter initializers are transitively disallowed in
|
|
// ambient contexts.
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseParameterInitializer() {
|
|
return parseInitializer(/*inParameter*/ true);
|
|
}
|
|
|
|
function fillSignature(
|
|
returnToken: SyntaxKind,
|
|
yieldAndGeneratorParameterContext: boolean,
|
|
requireCompleteParameterList: boolean,
|
|
signature: SignatureDeclaration): void {
|
|
let returnTokenRequired = returnToken === SyntaxKind.EqualsGreaterThanToken;
|
|
signature.typeParameters = parseTypeParameters();
|
|
signature.parameters = parseParameterList(yieldAndGeneratorParameterContext, requireCompleteParameterList);
|
|
|
|
if (returnTokenRequired) {
|
|
parseExpected(returnToken);
|
|
signature.type = parseType();
|
|
}
|
|
else if (parseOptional(returnToken)) {
|
|
signature.type = parseType();
|
|
}
|
|
}
|
|
|
|
// Note: after careful analysis of the grammar, it does not appear to be possible to
|
|
// have 'Yield' And 'GeneratorParameter' not in sync. i.e. any production calling
|
|
// this FormalParameters production either always sets both to true, or always sets
|
|
// both to false. As such we only have a single parameter to represent both.
|
|
function parseParameterList(yieldAndGeneratorParameterContext: boolean, requireCompleteParameterList: boolean) {
|
|
// FormalParameters[Yield,GeneratorParameter] :
|
|
// ...
|
|
//
|
|
// FormalParameter[Yield,GeneratorParameter] :
|
|
// BindingElement[?Yield, ?GeneratorParameter]
|
|
//
|
|
// BindingElement[Yield, GeneratorParameter ] : See 13.2.3
|
|
// SingleNameBinding[?Yield, ?GeneratorParameter]
|
|
// [+GeneratorParameter]BindingPattern[?Yield, GeneratorParameter]Initializer[In]opt
|
|
// [~GeneratorParameter]BindingPattern[?Yield]Initializer[In, ?Yield]opt
|
|
//
|
|
// SingleNameBinding[Yield, GeneratorParameter] : See 13.2.3
|
|
// [+GeneratorParameter]BindingIdentifier[Yield]Initializer[In]opt
|
|
// [~GeneratorParameter]BindingIdentifier[?Yield]Initializer[In, ?Yield]opt
|
|
if (parseExpected(SyntaxKind.OpenParenToken)) {
|
|
let savedYieldContext = inYieldContext();
|
|
let savedGeneratorParameterContext = inGeneratorParameterContext();
|
|
|
|
setYieldContext(yieldAndGeneratorParameterContext);
|
|
setGeneratorParameterContext(yieldAndGeneratorParameterContext);
|
|
|
|
let result = parseDelimitedList(ParsingContext.Parameters, parseParameter);
|
|
|
|
setYieldContext(savedYieldContext);
|
|
setGeneratorParameterContext(savedGeneratorParameterContext);
|
|
|
|
if (!parseExpected(SyntaxKind.CloseParenToken) && requireCompleteParameterList) {
|
|
// Caller insisted that we had to end with a ) We didn't. So just return
|
|
// undefined here.
|
|
return undefined;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// We didn't even have an open paren. If the caller requires a complete parameter list,
|
|
// we definitely can't provide that. However, if they're ok with an incomplete one,
|
|
// then just return an empty set of parameters.
|
|
return requireCompleteParameterList ? undefined : createMissingList<ParameterDeclaration>();
|
|
}
|
|
|
|
function parseTypeMemberSemicolon() {
|
|
// We allow type members to be separated by commas or (possibly ASI) semicolons.
|
|
// First check if it was a comma. If so, we're done with the member.
|
|
if (parseOptional(SyntaxKind.CommaToken)) {
|
|
return;
|
|
}
|
|
|
|
// Didn't have a comma. We must have a (possible ASI) semicolon.
|
|
parseSemicolon();
|
|
}
|
|
|
|
function parseSignatureMember(kind: SyntaxKind): SignatureDeclaration {
|
|
let node = <SignatureDeclaration>createNode(kind);
|
|
if (kind === SyntaxKind.ConstructSignature) {
|
|
parseExpected(SyntaxKind.NewKeyword);
|
|
}
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ false, node);
|
|
parseTypeMemberSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function isIndexSignature(): boolean {
|
|
if (token !== SyntaxKind.OpenBracketToken) {
|
|
return false;
|
|
}
|
|
|
|
return lookAhead(isUnambiguouslyIndexSignature);
|
|
}
|
|
|
|
function isUnambiguouslyIndexSignature() {
|
|
// The only allowed sequence is:
|
|
//
|
|
// [id:
|
|
//
|
|
// However, for error recovery, we also check the following cases:
|
|
//
|
|
// [...
|
|
// [id,
|
|
// [id?,
|
|
// [id?:
|
|
// [id?]
|
|
// [public id
|
|
// [private id
|
|
// [protected id
|
|
// []
|
|
//
|
|
nextToken();
|
|
if (token === SyntaxKind.DotDotDotToken || token === SyntaxKind.CloseBracketToken) {
|
|
return true;
|
|
}
|
|
|
|
if (isModifier(token)) {
|
|
nextToken();
|
|
if (isIdentifier()) {
|
|
return true;
|
|
}
|
|
}
|
|
else if (!isIdentifier()) {
|
|
return false;
|
|
}
|
|
else {
|
|
// Skip the identifier
|
|
nextToken();
|
|
}
|
|
|
|
// A colon signifies a well formed indexer
|
|
// A comma should be a badly formed indexer because comma expressions are not allowed
|
|
// in computed properties.
|
|
if (token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken) {
|
|
return true;
|
|
}
|
|
|
|
// Question mark could be an indexer with an optional property,
|
|
// or it could be a conditional expression in a computed property.
|
|
if (token !== SyntaxKind.QuestionToken) {
|
|
return false;
|
|
}
|
|
|
|
// If any of the following tokens are after the question mark, it cannot
|
|
// be a conditional expression, so treat it as an indexer.
|
|
nextToken();
|
|
return token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBracketToken;
|
|
}
|
|
|
|
function parseIndexSignatureDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): IndexSignatureDeclaration {
|
|
let node = <IndexSignatureDeclaration>createNode(SyntaxKind.IndexSignature, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
node.parameters = parseBracketedList(ParsingContext.Parameters, parseParameter, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
|
|
node.type = parseTypeAnnotation();
|
|
parseTypeMemberSemicolon();
|
|
return finishNode(node)
|
|
}
|
|
|
|
function parsePropertyOrMethodSignature(): Declaration {
|
|
let fullStart = scanner.getStartPos();
|
|
let name = parsePropertyName();
|
|
let questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
|
|
|
|
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
|
|
let method = <MethodDeclaration>createNode(SyntaxKind.MethodSignature, fullStart);
|
|
method.name = name;
|
|
method.questionToken = questionToken;
|
|
|
|
// Method signatues don't exist in expression contexts. So they have neither
|
|
// [Yield] nor [GeneratorParameter]
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ false, method);
|
|
parseTypeMemberSemicolon();
|
|
return finishNode(method);
|
|
}
|
|
else {
|
|
let property = <PropertyDeclaration>createNode(SyntaxKind.PropertySignature, fullStart);
|
|
property.name = name;
|
|
property.questionToken = questionToken;
|
|
property.type = parseTypeAnnotation();
|
|
parseTypeMemberSemicolon();
|
|
return finishNode(property);
|
|
}
|
|
}
|
|
|
|
function isStartOfTypeMember(): boolean {
|
|
switch (token) {
|
|
case SyntaxKind.OpenParenToken:
|
|
case SyntaxKind.LessThanToken:
|
|
case SyntaxKind.OpenBracketToken: // Both for indexers and computed properties
|
|
return true;
|
|
default:
|
|
if (isModifier(token)) {
|
|
let result = lookAhead(isStartOfIndexSignatureDeclaration);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return isLiteralPropertyName() && lookAhead(isTypeMemberWithLiteralPropertyName);
|
|
}
|
|
}
|
|
|
|
function isStartOfIndexSignatureDeclaration() {
|
|
while (isModifier(token)) {
|
|
nextToken();
|
|
}
|
|
|
|
return isIndexSignature();
|
|
}
|
|
|
|
function isTypeMemberWithLiteralPropertyName() {
|
|
nextToken();
|
|
return token === SyntaxKind.OpenParenToken ||
|
|
token === SyntaxKind.LessThanToken ||
|
|
token === SyntaxKind.QuestionToken ||
|
|
token === SyntaxKind.ColonToken ||
|
|
canParseSemicolon();
|
|
}
|
|
|
|
function parseTypeMember(): Declaration {
|
|
switch (token) {
|
|
case SyntaxKind.OpenParenToken:
|
|
case SyntaxKind.LessThanToken:
|
|
return parseSignatureMember(SyntaxKind.CallSignature);
|
|
case SyntaxKind.OpenBracketToken:
|
|
// Indexer or computed property
|
|
return isIndexSignature()
|
|
? parseIndexSignatureDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined)
|
|
: parsePropertyOrMethodSignature();
|
|
case SyntaxKind.NewKeyword:
|
|
if (lookAhead(isStartOfConstructSignature)) {
|
|
return parseSignatureMember(SyntaxKind.ConstructSignature);
|
|
}
|
|
// fall through.
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
return parsePropertyOrMethodSignature();
|
|
default:
|
|
// Index declaration as allowed as a type member. But as per the grammar,
|
|
// they also allow modifiers. So we have to check for an index declaration
|
|
// that might be following modifiers. This ensures that things work properly
|
|
// when incrementally parsing as the parser will produce the Index declaration
|
|
// if it has the same text regardless of whether it is inside a class or an
|
|
// object type.
|
|
if (isModifier(token)) {
|
|
let result = tryParse(parseIndexSignatureWithModifiers);
|
|
if (result) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (isIdentifierOrKeyword()) {
|
|
return parsePropertyOrMethodSignature();
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseIndexSignatureWithModifiers() {
|
|
let fullStart = scanner.getStartPos();
|
|
let decorators = parseDecorators();
|
|
let modifiers = parseModifiers();
|
|
return isIndexSignature()
|
|
? parseIndexSignatureDeclaration(fullStart, decorators, modifiers)
|
|
: undefined;
|
|
}
|
|
|
|
function isStartOfConstructSignature() {
|
|
nextToken();
|
|
return token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken;
|
|
}
|
|
|
|
function parseTypeLiteral(): TypeLiteralNode {
|
|
let node = <TypeLiteralNode>createNode(SyntaxKind.TypeLiteral);
|
|
node.members = parseObjectTypeMembers();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseObjectTypeMembers(): NodeArray<Declaration> {
|
|
let members: NodeArray<Declaration>;
|
|
if (parseExpected(SyntaxKind.OpenBraceToken)) {
|
|
members = parseList(ParsingContext.TypeMembers, /*checkForStrictMode*/ false, parseTypeMember);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
}
|
|
else {
|
|
members = createMissingList<Declaration>();
|
|
}
|
|
|
|
return members;
|
|
}
|
|
|
|
function parseTupleType(): TupleTypeNode {
|
|
let node = <TupleTypeNode>createNode(SyntaxKind.TupleType);
|
|
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseParenthesizedType(): ParenthesizedTypeNode {
|
|
let node = <ParenthesizedTypeNode>createNode(SyntaxKind.ParenthesizedType);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.type = parseType();
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseFunctionOrConstructorType(kind: SyntaxKind): FunctionOrConstructorTypeNode {
|
|
let node = <FunctionOrConstructorTypeNode>createNode(kind);
|
|
if (kind === SyntaxKind.ConstructorType) {
|
|
parseExpected(SyntaxKind.NewKeyword);
|
|
}
|
|
fillSignature(SyntaxKind.EqualsGreaterThanToken, /*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ false, node);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseKeywordAndNoDot(): TypeNode {
|
|
let node = parseTokenNode<TypeNode>();
|
|
return token === SyntaxKind.DotToken ? undefined : node;
|
|
}
|
|
|
|
function parseNonArrayType(): TypeNode {
|
|
switch (token) {
|
|
case SyntaxKind.AnyKeyword:
|
|
case SyntaxKind.StringKeyword:
|
|
case SyntaxKind.NumberKeyword:
|
|
case SyntaxKind.BooleanKeyword:
|
|
case SyntaxKind.SymbolKeyword:
|
|
// If these are followed by a dot, then parse these out as a dotted type reference instead.
|
|
let node = tryParse(parseKeywordAndNoDot);
|
|
return node || parseTypeReference();
|
|
case SyntaxKind.VoidKeyword:
|
|
return parseTokenNode<TypeNode>();
|
|
case SyntaxKind.TypeOfKeyword:
|
|
return parseTypeQuery();
|
|
case SyntaxKind.OpenBraceToken:
|
|
return parseTypeLiteral();
|
|
case SyntaxKind.OpenBracketToken:
|
|
return parseTupleType();
|
|
case SyntaxKind.OpenParenToken:
|
|
return parseParenthesizedType();
|
|
default:
|
|
return parseTypeReference();
|
|
}
|
|
}
|
|
|
|
function isStartOfType(): boolean {
|
|
switch (token) {
|
|
case SyntaxKind.AnyKeyword:
|
|
case SyntaxKind.StringKeyword:
|
|
case SyntaxKind.NumberKeyword:
|
|
case SyntaxKind.BooleanKeyword:
|
|
case SyntaxKind.SymbolKeyword:
|
|
case SyntaxKind.VoidKeyword:
|
|
case SyntaxKind.TypeOfKeyword:
|
|
case SyntaxKind.OpenBraceToken:
|
|
case SyntaxKind.OpenBracketToken:
|
|
case SyntaxKind.LessThanToken:
|
|
case SyntaxKind.NewKeyword:
|
|
return true;
|
|
case SyntaxKind.OpenParenToken:
|
|
// Only consider '(' the start of a type if followed by ')', '...', an identifier, a modifier,
|
|
// or something that starts a type. We don't want to consider things like '(1)' a type.
|
|
return lookAhead(isStartOfParenthesizedOrFunctionType);
|
|
default:
|
|
return isIdentifier();
|
|
}
|
|
}
|
|
|
|
function isStartOfParenthesizedOrFunctionType() {
|
|
nextToken();
|
|
return token === SyntaxKind.CloseParenToken || isStartOfParameter() || isStartOfType();
|
|
}
|
|
|
|
function parseArrayTypeOrHigher(): TypeNode {
|
|
let type = parseNonArrayType();
|
|
while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) {
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
let node = <ArrayTypeNode>createNode(SyntaxKind.ArrayType, type.pos);
|
|
node.elementType = type;
|
|
type = finishNode(node);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function parseUnionTypeOrHigher(): TypeNode {
|
|
let type = parseArrayTypeOrHigher();
|
|
if (token === SyntaxKind.BarToken) {
|
|
let types = <NodeArray<TypeNode>>[type];
|
|
types.pos = type.pos;
|
|
while (parseOptional(SyntaxKind.BarToken)) {
|
|
types.push(parseArrayTypeOrHigher());
|
|
}
|
|
types.end = getNodeEnd();
|
|
let node = <UnionTypeNode>createNode(SyntaxKind.UnionType, type.pos);
|
|
node.types = types;
|
|
type = finishNode(node);
|
|
}
|
|
return type;
|
|
}
|
|
|
|
function isStartOfFunctionType(): boolean {
|
|
if (token === SyntaxKind.LessThanToken) {
|
|
return true;
|
|
}
|
|
|
|
return token === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType);
|
|
}
|
|
|
|
function isUnambiguouslyStartOfFunctionType() {
|
|
nextToken();
|
|
if (token === SyntaxKind.CloseParenToken || token === SyntaxKind.DotDotDotToken) {
|
|
// ( )
|
|
// ( ...
|
|
return true;
|
|
}
|
|
if (isIdentifier() || isModifier(token)) {
|
|
nextToken();
|
|
if (token === SyntaxKind.ColonToken || token === SyntaxKind.CommaToken ||
|
|
token === SyntaxKind.QuestionToken || token === SyntaxKind.EqualsToken ||
|
|
isIdentifier() || isModifier(token)) {
|
|
// ( id :
|
|
// ( id ,
|
|
// ( id ?
|
|
// ( id =
|
|
// ( modifier id
|
|
return true;
|
|
}
|
|
if (token === SyntaxKind.CloseParenToken) {
|
|
nextToken();
|
|
if (token === SyntaxKind.EqualsGreaterThanToken) {
|
|
// ( id ) =>
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function parseType(): TypeNode {
|
|
// The rules about 'yield' only apply to actual code/expression contexts. They don't
|
|
// apply to 'type' contexts. So we disable these parameters here before moving on.
|
|
let savedYieldContext = inYieldContext();
|
|
let savedGeneratorParameterContext = inGeneratorParameterContext();
|
|
|
|
setYieldContext(false);
|
|
setGeneratorParameterContext(false);
|
|
|
|
let result = parseTypeWorker();
|
|
|
|
setYieldContext(savedYieldContext);
|
|
setGeneratorParameterContext(savedGeneratorParameterContext);
|
|
|
|
return result;
|
|
}
|
|
|
|
function parseTypeWorker(): TypeNode {
|
|
if (isStartOfFunctionType()) {
|
|
return parseFunctionOrConstructorType(SyntaxKind.FunctionType);
|
|
}
|
|
if (token === SyntaxKind.NewKeyword) {
|
|
return parseFunctionOrConstructorType(SyntaxKind.ConstructorType);
|
|
}
|
|
return parseUnionTypeOrHigher();
|
|
}
|
|
|
|
function parseTypeAnnotation(): TypeNode {
|
|
return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined;
|
|
}
|
|
|
|
// EXPRESSIONS
|
|
function isStartOfLeftHandSideExpression(): boolean {
|
|
switch (token) {
|
|
case SyntaxKind.ThisKeyword:
|
|
case SyntaxKind.SuperKeyword:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
case SyntaxKind.TemplateHead:
|
|
case SyntaxKind.OpenParenToken:
|
|
case SyntaxKind.OpenBracketToken:
|
|
case SyntaxKind.OpenBraceToken:
|
|
case SyntaxKind.FunctionKeyword:
|
|
case SyntaxKind.ClassKeyword:
|
|
case SyntaxKind.NewKeyword:
|
|
case SyntaxKind.SlashToken:
|
|
case SyntaxKind.SlashEqualsToken:
|
|
case SyntaxKind.Identifier:
|
|
return true;
|
|
default:
|
|
return isIdentifier();
|
|
}
|
|
}
|
|
|
|
function isStartOfExpression(): boolean {
|
|
if (isStartOfLeftHandSideExpression()) {
|
|
return true;
|
|
}
|
|
|
|
switch (token) {
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.MinusToken:
|
|
case SyntaxKind.TildeToken:
|
|
case SyntaxKind.ExclamationToken:
|
|
case SyntaxKind.DeleteKeyword:
|
|
case SyntaxKind.TypeOfKeyword:
|
|
case SyntaxKind.VoidKeyword:
|
|
case SyntaxKind.PlusPlusToken:
|
|
case SyntaxKind.MinusMinusToken:
|
|
case SyntaxKind.LessThanToken:
|
|
case SyntaxKind.YieldKeyword:
|
|
// Yield always starts an expression. Either it is an identifier (in which case
|
|
// it is definitely an expression). Or it's a keyword (either because we're in
|
|
// a generator, or in strict mode (or both)) and it started a yield expression.
|
|
return true;
|
|
default:
|
|
// Error tolerance. If we see the start of some binary operator, we consider
|
|
// that the start of an expression. That way we'll parse out a missing identifier,
|
|
// give a good message about an identifier being missing, and then consume the
|
|
// rest of the binary expression.
|
|
if (isBinaryOperator()) {
|
|
return true;
|
|
}
|
|
|
|
return isIdentifier();
|
|
}
|
|
}
|
|
|
|
function isStartOfExpressionStatement(): boolean {
|
|
// As per the grammar, none of '{' or 'function' or 'class' can start an expression statement.
|
|
return token !== SyntaxKind.OpenBraceToken &&
|
|
token !== SyntaxKind.FunctionKeyword &&
|
|
token !== SyntaxKind.ClassKeyword &&
|
|
token !== SyntaxKind.AtToken &&
|
|
isStartOfExpression();
|
|
}
|
|
|
|
function parseExpression(): Expression {
|
|
// Expression[in]:
|
|
// AssignmentExpression[in]
|
|
// Expression[in] , AssignmentExpression[in]
|
|
|
|
// clear the decorator context when parsing Expression, as it should be unambiguous when parsing a decorator
|
|
let saveDecoratorContext = inDecoratorContext();
|
|
if (saveDecoratorContext) {
|
|
setDecoratorContext(false);
|
|
}
|
|
|
|
let expr = parseAssignmentExpressionOrHigher();
|
|
let operatorToken: Node;
|
|
while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) {
|
|
expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher());
|
|
}
|
|
|
|
if (saveDecoratorContext) {
|
|
setDecoratorContext(true);
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
function parseInitializer(inParameter: boolean): Expression {
|
|
if (token !== SyntaxKind.EqualsToken) {
|
|
// It's not uncommon during typing for the user to miss writing the '=' token. Check if
|
|
// there is no newline after the last token and if we're on an expression. If so, parse
|
|
// this as an equals-value clause with a missing equals.
|
|
// NOTE: There are two places where we allow equals-value clauses. The first is in a
|
|
// variable declarator. The second is with a parameter. For variable declarators
|
|
// it's more likely that a { would be a allowed (as an object literal). While this
|
|
// is also allowed for parameters, the risk is that we consume the { as an object
|
|
// literal when it really will be for the block following the parameter.
|
|
if (scanner.hasPrecedingLineBreak() || (inParameter && token === SyntaxKind.OpenBraceToken) || !isStartOfExpression()) {
|
|
// preceding line break, open brace in a parameter (likely a function body) or current token is not an expression -
|
|
// do not try to parse initializer
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
// Initializer[In, Yield] :
|
|
// = AssignmentExpression[?In, ?Yield]
|
|
|
|
parseExpected(SyntaxKind.EqualsToken);
|
|
return parseAssignmentExpressionOrHigher();
|
|
}
|
|
|
|
function parseAssignmentExpressionOrHigher(): Expression {
|
|
// AssignmentExpression[in,yield]:
|
|
// 1) ConditionalExpression[?in,?yield]
|
|
// 2) LeftHandSideExpression = AssignmentExpression[?in,?yield]
|
|
// 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield]
|
|
// 4) ArrowFunctionExpression[?in,?yield]
|
|
// 5) [+Yield] YieldExpression[?In]
|
|
//
|
|
// Note: for ease of implementation we treat productions '2' and '3' as the same thing.
|
|
// (i.e. they're both BinaryExpressions with an assignment operator in it).
|
|
|
|
// First, do the simple check if we have a YieldExpression (production '5').
|
|
if (isYieldExpression()) {
|
|
return parseYieldExpression();
|
|
}
|
|
|
|
// Then, check if we have an arrow function (production '4') that starts with a parenthesized
|
|
// parameter list. If we do, we must *not* recurse for productions 1, 2 or 3. An ArrowFunction is
|
|
// not a LeftHandSideExpression, nor does it start a ConditionalExpression. So we are done
|
|
// with AssignmentExpression if we see one.
|
|
let arrowExpression = tryParseParenthesizedArrowFunctionExpression();
|
|
if (arrowExpression) {
|
|
return arrowExpression;
|
|
}
|
|
|
|
// Now try to see if we're in production '1', '2' or '3'. A conditional expression can
|
|
// start with a LogicalOrExpression, while the assignment productions can only start with
|
|
// LeftHandSideExpressions.
|
|
//
|
|
// So, first, we try to just parse out a BinaryExpression. If we get something that is a
|
|
// LeftHandSide or higher, then we can try to parse out the assignment expression part.
|
|
// Otherwise, we try to parse out the conditional expression bit. We want to allow any
|
|
// binary expression here, so we pass in the 'lowest' precedence here so that it matches
|
|
// and consumes anything.
|
|
let expr = parseBinaryExpressionOrHigher(/*precedence*/ 0);
|
|
|
|
// To avoid a look-ahead, we did not handle the case of an arrow function with a single un-parenthesized
|
|
// parameter ('x => ...') above. We handle it here by checking if the parsed expression was a single
|
|
// identifier and the current token is an arrow.
|
|
if (expr.kind === SyntaxKind.Identifier && token === SyntaxKind.EqualsGreaterThanToken) {
|
|
return parseSimpleArrowFunctionExpression(<Identifier>expr);
|
|
}
|
|
|
|
// Now see if we might be in cases '2' or '3'.
|
|
// If the expression was a LHS expression, and we have an assignment operator, then
|
|
// we're in '2' or '3'. Consume the assignment and return.
|
|
//
|
|
// Note: we call reScanGreaterToken so that we get an appropriately merged token
|
|
// for cases like > > = becoming >>=
|
|
if (isLeftHandSideExpression(expr) && isAssignmentOperator(reScanGreaterToken())) {
|
|
return makeBinaryExpression(expr, parseTokenNode(), parseAssignmentExpressionOrHigher());
|
|
}
|
|
|
|
// It wasn't an assignment or a lambda. This is a conditional expression:
|
|
return parseConditionalExpressionRest(expr);
|
|
}
|
|
|
|
function isYieldExpression(): boolean {
|
|
if (token === SyntaxKind.YieldKeyword) {
|
|
// If we have a 'yield' keyword, and htis is a context where yield expressions are
|
|
// allowed, then definitely parse out a yield expression.
|
|
if (inYieldContext()) {
|
|
return true;
|
|
}
|
|
|
|
if (inStrictModeContext()) {
|
|
// If we're in strict mode, then 'yield' is a keyword, could only ever start
|
|
// a yield expression.
|
|
return true;
|
|
}
|
|
|
|
// We're in a context where 'yield expr' is not allowed. However, if we can
|
|
// definitely tell that the user was trying to parse a 'yield expr' and not
|
|
// just a normal expr that start with a 'yield' identifier, then parse out
|
|
// a 'yield expr'. We can then report an error later that they are only
|
|
// allowed in generator expressions.
|
|
//
|
|
// for example, if we see 'yield(foo)', then we'll have to treat that as an
|
|
// invocation expression of something called 'yield'. However, if we have
|
|
// 'yield foo' then that is not legal as a normal expression, so we can
|
|
// definitely recognize this as a yield expression.
|
|
//
|
|
// for now we just check if the next token is an identifier. More heuristics
|
|
// can be added here later as necessary. We just need to make sure that we
|
|
// don't accidently consume something legal.
|
|
return lookAhead(nextTokenIsIdentifierOnSameLine);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function nextTokenIsIdentifierOnSameLine() {
|
|
nextToken();
|
|
return !scanner.hasPrecedingLineBreak() && isIdentifier()
|
|
}
|
|
|
|
function parseYieldExpression(): YieldExpression {
|
|
let node = <YieldExpression>createNode(SyntaxKind.YieldExpression);
|
|
|
|
// YieldExpression[In] :
|
|
// yield
|
|
// yield [no LineTerminator here] [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield]
|
|
// yield [no LineTerminator here] * [Lexical goal InputElementRegExp]AssignmentExpression[?In, Yield]
|
|
nextToken();
|
|
|
|
if (!scanner.hasPrecedingLineBreak() &&
|
|
(token === SyntaxKind.AsteriskToken || isStartOfExpression())) {
|
|
node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
|
|
node.expression = parseAssignmentExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
else {
|
|
// if the next token is not on the same line as yield. or we don't have an '*' or
|
|
// the start of an expressin, then this is just a simple "yield" expression.
|
|
return finishNode(node);
|
|
}
|
|
}
|
|
|
|
function parseSimpleArrowFunctionExpression(identifier: Identifier): Expression {
|
|
Debug.assert(token === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>");
|
|
|
|
let node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction, identifier.pos);
|
|
|
|
let parameter = <ParameterDeclaration>createNode(SyntaxKind.Parameter, identifier.pos);
|
|
parameter.name = identifier;
|
|
finishNode(parameter);
|
|
|
|
node.parameters = <NodeArray<ParameterDeclaration>>[parameter];
|
|
node.parameters.pos = parameter.pos;
|
|
node.parameters.end = parameter.end;
|
|
|
|
node.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, false, Diagnostics._0_expected, "=>");
|
|
node.body = parseArrowFunctionExpressionBody();
|
|
|
|
return finishNode(node);
|
|
}
|
|
|
|
function tryParseParenthesizedArrowFunctionExpression(): Expression {
|
|
let triState = isParenthesizedArrowFunctionExpression();
|
|
|
|
if (triState === Tristate.False) {
|
|
// It's definitely not a parenthesized arrow function expression.
|
|
return undefined;
|
|
}
|
|
|
|
// If we definitely have an arrow function, then we can just parse one, not requiring a
|
|
// following => or { token. Otherwise, we *might* have an arrow function. Try to parse
|
|
// it out, but don't allow any ambiguity, and return 'undefined' if this could be an
|
|
// expression instead.
|
|
let arrowFunction = triState === Tristate.True
|
|
? parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ true)
|
|
: tryParse(parsePossibleParenthesizedArrowFunctionExpressionHead);
|
|
|
|
if (!arrowFunction) {
|
|
// Didn't appear to actually be a parenthesized arrow function. Just bail out.
|
|
return undefined;
|
|
}
|
|
|
|
// If we have an arrow, then try to parse the body. Even if not, try to parse if we
|
|
// have an opening brace, just in case we're in an error state.
|
|
var lastToken = token;
|
|
arrowFunction.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, /*reportAtCurrentPosition*/false, Diagnostics._0_expected, "=>");
|
|
arrowFunction.body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken)
|
|
? parseArrowFunctionExpressionBody()
|
|
: parseIdentifier();
|
|
|
|
return finishNode(arrowFunction);
|
|
}
|
|
|
|
// True -> We definitely expect a parenthesized arrow function here.
|
|
// False -> There *cannot* be a parenthesized arrow function here.
|
|
// Unknown -> There *might* be a parenthesized arrow function here.
|
|
// Speculatively look ahead to be sure, and rollback if not.
|
|
function isParenthesizedArrowFunctionExpression(): Tristate {
|
|
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
|
|
return lookAhead(isParenthesizedArrowFunctionExpressionWorker);
|
|
}
|
|
|
|
if (token === SyntaxKind.EqualsGreaterThanToken) {
|
|
// ERROR RECOVERY TWEAK:
|
|
// If we see a standalone => try to parse it as an arrow function expression as that's
|
|
// likely what the user intended to write.
|
|
return Tristate.True;
|
|
}
|
|
// Definitely not a parenthesized arrow function.
|
|
return Tristate.False;
|
|
}
|
|
|
|
function isParenthesizedArrowFunctionExpressionWorker() {
|
|
let first = token;
|
|
let second = nextToken();
|
|
|
|
if (first === SyntaxKind.OpenParenToken) {
|
|
if (second === SyntaxKind.CloseParenToken) {
|
|
// Simple cases: "() =>", "(): ", and "() {".
|
|
// This is an arrow function with no parameters.
|
|
// The last one is not actually an arrow function,
|
|
// but this is probably what the user intended.
|
|
let third = nextToken();
|
|
switch (third) {
|
|
case SyntaxKind.EqualsGreaterThanToken:
|
|
case SyntaxKind.ColonToken:
|
|
case SyntaxKind.OpenBraceToken:
|
|
return Tristate.True;
|
|
default:
|
|
return Tristate.False;
|
|
}
|
|
}
|
|
|
|
// If encounter "([" or "({", this could be the start of a binding pattern.
|
|
// Examples:
|
|
// ([ x ]) => { }
|
|
// ({ x }) => { }
|
|
// ([ x ])
|
|
// ({ x })
|
|
if (second === SyntaxKind.OpenBracketToken || second === SyntaxKind.OpenBraceToken) {
|
|
return Tristate.Unknown;
|
|
}
|
|
|
|
// Simple case: "(..."
|
|
// This is an arrow function with a rest parameter.
|
|
if (second === SyntaxKind.DotDotDotToken) {
|
|
return Tristate.True;
|
|
}
|
|
|
|
// If we had "(" followed by something that's not an identifier,
|
|
// then this definitely doesn't look like a lambda.
|
|
// Note: we could be a little more lenient and allow
|
|
// "(public" or "(private". These would not ever actually be allowed,
|
|
// but we could provide a good error message instead of bailing out.
|
|
if (!isIdentifier()) {
|
|
return Tristate.False;
|
|
}
|
|
|
|
// If we have something like "(a:", then we must have a
|
|
// type-annotated parameter in an arrow function expression.
|
|
if (nextToken() === SyntaxKind.ColonToken) {
|
|
return Tristate.True;
|
|
}
|
|
|
|
// This *could* be a parenthesized arrow function.
|
|
// Return Unknown to let the caller know.
|
|
return Tristate.Unknown;
|
|
}
|
|
else {
|
|
Debug.assert(first === SyntaxKind.LessThanToken);
|
|
|
|
// If we have "<" not followed by an identifier,
|
|
// then this definitely is not an arrow function.
|
|
if (!isIdentifier()) {
|
|
return Tristate.False;
|
|
}
|
|
|
|
// This *could* be a parenthesized arrow function.
|
|
return Tristate.Unknown;
|
|
}
|
|
}
|
|
|
|
function parsePossibleParenthesizedArrowFunctionExpressionHead(): ArrowFunction {
|
|
return parseParenthesizedArrowFunctionExpressionHead(/*allowAmbiguity*/ false);
|
|
}
|
|
|
|
function parseParenthesizedArrowFunctionExpressionHead(allowAmbiguity: boolean): ArrowFunction {
|
|
let node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction);
|
|
// Arrow functions are never generators.
|
|
//
|
|
// If we're speculatively parsing a signature for a parenthesized arrow function, then
|
|
// we have to have a complete parameter list. Otherwise we might see something like
|
|
// a => (b => c)
|
|
// And think that "(b =>" was actually a parenthesized arrow function with a missing
|
|
// close paren.
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ !allowAmbiguity, node);
|
|
|
|
// If we couldn't get parameters, we definitely could not parse out an arrow function.
|
|
if (!node.parameters) {
|
|
return undefined;
|
|
}
|
|
|
|
// Parsing a signature isn't enough.
|
|
// Parenthesized arrow signatures often look like other valid expressions.
|
|
// For instance:
|
|
// - "(x = 10)" is an assignment expression parsed as a signature with a default parameter value.
|
|
// - "(x,y)" is a comma expression parsed as a signature with two parameters.
|
|
// - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation.
|
|
//
|
|
// So we need just a bit of lookahead to ensure that it can only be a signature.
|
|
if (!allowAmbiguity && token !== SyntaxKind.EqualsGreaterThanToken && token !== SyntaxKind.OpenBraceToken) {
|
|
// Returning undefined here will cause our caller to rewind to where we started from.
|
|
return undefined;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
function parseArrowFunctionExpressionBody(): Block | Expression {
|
|
if (token === SyntaxKind.OpenBraceToken) {
|
|
return parseFunctionBlock(/*allowYield*/ false, /*ignoreMissingOpenBrace*/ false);
|
|
}
|
|
|
|
if (token !== SyntaxKind.SemicolonToken &&
|
|
token !== SyntaxKind.FunctionKeyword &&
|
|
token !== SyntaxKind.ClassKeyword &&
|
|
isStartOfStatement() &&
|
|
!isStartOfExpressionStatement()) {
|
|
// Check if we got a plain statement (i.e. no expression-statements, no function/class expressions/declarations)
|
|
//
|
|
// Here we try to recover from a potential error situation in the case where the
|
|
// user meant to supply a block. For example, if the user wrote:
|
|
//
|
|
// a =>
|
|
// let v = 0;
|
|
// }
|
|
//
|
|
// they may be missing an open brace. Check to see if that's the case so we can
|
|
// try to recover better. If we don't do this, then the next close curly we see may end
|
|
// up preemptively closing the containing construct.
|
|
//
|
|
// Note: even when 'ignoreMissingOpenBrace' is passed as true, parseBody will still error.
|
|
return parseFunctionBlock(/*allowYield*/ false, /*ignoreMissingOpenBrace*/ true);
|
|
}
|
|
|
|
return parseAssignmentExpressionOrHigher();
|
|
}
|
|
|
|
function parseConditionalExpressionRest(leftOperand: Expression): Expression {
|
|
// Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher.
|
|
let questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
|
|
if (!questionToken) {
|
|
return leftOperand;
|
|
}
|
|
|
|
// Note: we explicitly 'allowIn' in the whenTrue part of the condition expression, and
|
|
// we do not that for the 'whenFalse' part.
|
|
let node = <ConditionalExpression>createNode(SyntaxKind.ConditionalExpression, leftOperand.pos);
|
|
node.condition = leftOperand;
|
|
node.questionToken = questionToken;
|
|
node.whenTrue = doOutsideOfContext(disallowInAndDecoratorContext, parseAssignmentExpressionOrHigher);
|
|
node.colonToken = parseExpectedToken(SyntaxKind.ColonToken, /*reportAtCurrentPosition*/ false,
|
|
Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken));
|
|
node.whenFalse = parseAssignmentExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseBinaryExpressionOrHigher(precedence: number): Expression {
|
|
let leftOperand = parseUnaryExpressionOrHigher();
|
|
return parseBinaryExpressionRest(precedence, leftOperand);
|
|
}
|
|
|
|
function isInOrOfKeyword(t: SyntaxKind) {
|
|
return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword;
|
|
}
|
|
|
|
function parseBinaryExpressionRest(precedence: number, leftOperand: Expression): Expression {
|
|
while (true) {
|
|
// We either have a binary operator here, or we're finished. We call
|
|
// reScanGreaterToken so that we merge token sequences like > and = into >=
|
|
|
|
reScanGreaterToken();
|
|
let newPrecedence = getBinaryOperatorPrecedence();
|
|
|
|
// Check the precedence to see if we should "take" this operator
|
|
if (newPrecedence <= precedence) {
|
|
break;
|
|
}
|
|
|
|
if (token === SyntaxKind.InKeyword && inDisallowInContext()) {
|
|
break;
|
|
}
|
|
|
|
leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence));
|
|
}
|
|
|
|
return leftOperand;
|
|
}
|
|
|
|
function isBinaryOperator() {
|
|
if (inDisallowInContext() && token === SyntaxKind.InKeyword) {
|
|
return false;
|
|
}
|
|
|
|
return getBinaryOperatorPrecedence() > 0;
|
|
}
|
|
|
|
function getBinaryOperatorPrecedence(): number {
|
|
switch (token) {
|
|
case SyntaxKind.BarBarToken:
|
|
return 1;
|
|
case SyntaxKind.AmpersandAmpersandToken:
|
|
return 2;
|
|
case SyntaxKind.BarToken:
|
|
return 3;
|
|
case SyntaxKind.CaretToken:
|
|
return 4;
|
|
case SyntaxKind.AmpersandToken:
|
|
return 5;
|
|
case SyntaxKind.EqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsToken:
|
|
case SyntaxKind.EqualsEqualsEqualsToken:
|
|
case SyntaxKind.ExclamationEqualsEqualsToken:
|
|
return 6;
|
|
case SyntaxKind.LessThanToken:
|
|
case SyntaxKind.GreaterThanToken:
|
|
case SyntaxKind.LessThanEqualsToken:
|
|
case SyntaxKind.GreaterThanEqualsToken:
|
|
case SyntaxKind.InstanceOfKeyword:
|
|
case SyntaxKind.InKeyword:
|
|
return 7;
|
|
case SyntaxKind.LessThanLessThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanToken:
|
|
case SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
|
return 8;
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.MinusToken:
|
|
return 9;
|
|
case SyntaxKind.AsteriskToken:
|
|
case SyntaxKind.SlashToken:
|
|
case SyntaxKind.PercentToken:
|
|
return 10;
|
|
}
|
|
|
|
// -1 is lower than all other precedences. Returning it will cause binary expression
|
|
// parsing to stop.
|
|
return -1;
|
|
}
|
|
|
|
function makeBinaryExpression(left: Expression, operatorToken: Node, right: Expression): BinaryExpression {
|
|
let node = <BinaryExpression>createNode(SyntaxKind.BinaryExpression, left.pos);
|
|
node.left = left;
|
|
node.operatorToken = operatorToken;
|
|
node.right = right;
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parsePrefixUnaryExpression() {
|
|
let node = <PrefixUnaryExpression>createNode(SyntaxKind.PrefixUnaryExpression);
|
|
node.operator = token;
|
|
nextToken();
|
|
node.operand = parseUnaryExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseDeleteExpression() {
|
|
let node = <DeleteExpression>createNode(SyntaxKind.DeleteExpression);
|
|
nextToken();
|
|
node.expression = parseUnaryExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseTypeOfExpression() {
|
|
let node = <TypeOfExpression>createNode(SyntaxKind.TypeOfExpression);
|
|
nextToken();
|
|
node.expression = parseUnaryExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseVoidExpression() {
|
|
let node = <VoidExpression>createNode(SyntaxKind.VoidExpression);
|
|
nextToken();
|
|
node.expression = parseUnaryExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseUnaryExpressionOrHigher(): UnaryExpression {
|
|
switch (token) {
|
|
case SyntaxKind.PlusToken:
|
|
case SyntaxKind.MinusToken:
|
|
case SyntaxKind.TildeToken:
|
|
case SyntaxKind.ExclamationToken:
|
|
case SyntaxKind.PlusPlusToken:
|
|
case SyntaxKind.MinusMinusToken:
|
|
return parsePrefixUnaryExpression();
|
|
case SyntaxKind.DeleteKeyword:
|
|
return parseDeleteExpression();
|
|
case SyntaxKind.TypeOfKeyword:
|
|
return parseTypeOfExpression();
|
|
case SyntaxKind.VoidKeyword:
|
|
return parseVoidExpression();
|
|
case SyntaxKind.LessThanToken:
|
|
return parseTypeAssertion();
|
|
default:
|
|
return parsePostfixExpressionOrHigher();
|
|
}
|
|
}
|
|
|
|
function parsePostfixExpressionOrHigher(): PostfixExpression {
|
|
let expression = parseLeftHandSideExpressionOrHigher();
|
|
|
|
Debug.assert(isLeftHandSideExpression(expression));
|
|
if ((token === SyntaxKind.PlusPlusToken || token === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) {
|
|
let node = <PostfixUnaryExpression>createNode(SyntaxKind.PostfixUnaryExpression, expression.pos);
|
|
node.operand = expression;
|
|
node.operator = token;
|
|
nextToken();
|
|
return finishNode(node);
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
|
|
function parseLeftHandSideExpressionOrHigher(): LeftHandSideExpression {
|
|
// Original Ecma:
|
|
// LeftHandSideExpression: See 11.2
|
|
// NewExpression
|
|
// CallExpression
|
|
//
|
|
// Our simplification:
|
|
//
|
|
// LeftHandSideExpression: See 11.2
|
|
// MemberExpression
|
|
// CallExpression
|
|
//
|
|
// See comment in parseMemberExpressionOrHigher on how we replaced NewExpression with
|
|
// MemberExpression to make our lives easier.
|
|
//
|
|
// to best understand the below code, it's important to see how CallExpression expands
|
|
// out into its own productions:
|
|
//
|
|
// CallExpression:
|
|
// MemberExpression Arguments
|
|
// CallExpression Arguments
|
|
// CallExpression[Expression]
|
|
// CallExpression.IdentifierName
|
|
// super ( ArgumentListopt )
|
|
// super.IdentifierName
|
|
//
|
|
// Because of the recursion in these calls, we need to bottom out first. There are two
|
|
// bottom out states we can run into. Either we see 'super' which must start either of
|
|
// the last two CallExpression productions. Or we have a MemberExpression which either
|
|
// completes the LeftHandSideExpression, or starts the beginning of the first four
|
|
// CallExpression productions.
|
|
let expression = token === SyntaxKind.SuperKeyword
|
|
? parseSuperExpression()
|
|
: parseMemberExpressionOrHigher();
|
|
|
|
// Now, we *may* be complete. However, we might have consumed the start of a
|
|
// CallExpression. As such, we need to consume the rest of it here to be complete.
|
|
return parseCallExpressionRest(expression);
|
|
}
|
|
|
|
function parseMemberExpressionOrHigher(): MemberExpression {
|
|
// Note: to make our lives simpler, we decompose the the NewExpression productions and
|
|
// place ObjectCreationExpression and FunctionExpression into PrimaryExpression.
|
|
// like so:
|
|
//
|
|
// PrimaryExpression : See 11.1
|
|
// this
|
|
// Identifier
|
|
// Literal
|
|
// ArrayLiteral
|
|
// ObjectLiteral
|
|
// (Expression)
|
|
// FunctionExpression
|
|
// new MemberExpression Arguments?
|
|
//
|
|
// MemberExpression : See 11.2
|
|
// PrimaryExpression
|
|
// MemberExpression[Expression]
|
|
// MemberExpression.IdentifierName
|
|
//
|
|
// CallExpression : See 11.2
|
|
// MemberExpression
|
|
// CallExpression Arguments
|
|
// CallExpression[Expression]
|
|
// CallExpression.IdentifierName
|
|
//
|
|
// Technically this is ambiguous. i.e. CallExpression defines:
|
|
//
|
|
// CallExpression:
|
|
// CallExpression Arguments
|
|
//
|
|
// If you see: "new Foo()"
|
|
//
|
|
// Then that could be treated as a single ObjectCreationExpression, or it could be
|
|
// treated as the invocation of "new Foo". We disambiguate that in code (to match
|
|
// the original grammar) by making sure that if we see an ObjectCreationExpression
|
|
// we always consume arguments if they are there. So we treat "new Foo()" as an
|
|
// object creation only, and not at all as an invocation) Another way to think
|
|
// about this is that for every "new" that we see, we will consume an argument list if
|
|
// it is there as part of the *associated* object creation node. Any additional
|
|
// argument lists we see, will become invocation expressions.
|
|
//
|
|
// Because there are no other places in the grammar now that refer to FunctionExpression
|
|
// or ObjectCreationExpression, it is safe to push down into the PrimaryExpression
|
|
// production.
|
|
//
|
|
// Because CallExpression and MemberExpression are left recursive, we need to bottom out
|
|
// of the recursion immediately. So we parse out a primary expression to start with.
|
|
let expression = parsePrimaryExpression();
|
|
return parseMemberExpressionRest(expression);
|
|
}
|
|
|
|
function parseSuperExpression(): MemberExpression {
|
|
let expression = parseTokenNode<PrimaryExpression>();
|
|
if (token === SyntaxKind.OpenParenToken || token === SyntaxKind.DotToken) {
|
|
return expression;
|
|
}
|
|
|
|
// If we have seen "super" it must be followed by '(' or '.'.
|
|
// If it wasn't then just try to parse out a '.' and report an error.
|
|
let node = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
|
|
node.expression = expression;
|
|
node.dotToken = parseExpectedToken(SyntaxKind.DotToken, /*reportAtCurrentPosition*/ false, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access);
|
|
node.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseTypeAssertion(): TypeAssertion {
|
|
let node = <TypeAssertion>createNode(SyntaxKind.TypeAssertionExpression);
|
|
parseExpected(SyntaxKind.LessThanToken);
|
|
node.type = parseType();
|
|
parseExpected(SyntaxKind.GreaterThanToken);
|
|
node.expression = parseUnaryExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseMemberExpressionRest(expression: LeftHandSideExpression): MemberExpression {
|
|
while (true) {
|
|
let dotToken = parseOptionalToken(SyntaxKind.DotToken);
|
|
if (dotToken) {
|
|
let propertyAccess = <PropertyAccessExpression>createNode(SyntaxKind.PropertyAccessExpression, expression.pos);
|
|
propertyAccess.expression = expression;
|
|
propertyAccess.dotToken = dotToken;
|
|
propertyAccess.name = parseRightSideOfDot(/*allowIdentifierNames*/ true);
|
|
expression = finishNode(propertyAccess);
|
|
continue;
|
|
}
|
|
|
|
// when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName
|
|
if (!inDecoratorContext() && parseOptional(SyntaxKind.OpenBracketToken)) {
|
|
let indexedAccess = <ElementAccessExpression>createNode(SyntaxKind.ElementAccessExpression, expression.pos);
|
|
indexedAccess.expression = expression;
|
|
|
|
// It's not uncommon for a user to write: "new Type[]".
|
|
// Check for that common pattern and report a better error message.
|
|
if (token !== SyntaxKind.CloseBracketToken) {
|
|
indexedAccess.argumentExpression = allowInAnd(parseExpression);
|
|
if (indexedAccess.argumentExpression.kind === SyntaxKind.StringLiteral || indexedAccess.argumentExpression.kind === SyntaxKind.NumericLiteral) {
|
|
let literal = <LiteralExpression>indexedAccess.argumentExpression;
|
|
literal.text = internIdentifier(literal.text);
|
|
}
|
|
}
|
|
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
expression = finishNode(indexedAccess);
|
|
continue;
|
|
}
|
|
|
|
if (token === SyntaxKind.NoSubstitutionTemplateLiteral || token === SyntaxKind.TemplateHead) {
|
|
let tagExpression = <TaggedTemplateExpression>createNode(SyntaxKind.TaggedTemplateExpression, expression.pos);
|
|
tagExpression.tag = expression;
|
|
tagExpression.template = token === SyntaxKind.NoSubstitutionTemplateLiteral
|
|
? parseLiteralNode()
|
|
: parseTemplateExpression();
|
|
expression = finishNode(tagExpression);
|
|
continue;
|
|
}
|
|
|
|
return <MemberExpression>expression;
|
|
}
|
|
}
|
|
|
|
function parseCallExpressionRest(expression: LeftHandSideExpression): LeftHandSideExpression {
|
|
while (true) {
|
|
expression = parseMemberExpressionRest(expression);
|
|
if (token === SyntaxKind.LessThanToken) {
|
|
// See if this is the start of a generic invocation. If so, consume it and
|
|
// keep checking for postfix expressions. Otherwise, it's just a '<' that's
|
|
// part of an arithmetic expression. Break out so we consume it higher in the
|
|
// stack.
|
|
let typeArguments = tryParse(parseTypeArgumentsInExpression);
|
|
if (!typeArguments) {
|
|
return expression;
|
|
}
|
|
|
|
let callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
|
|
callExpr.expression = expression;
|
|
callExpr.typeArguments = typeArguments;
|
|
callExpr.arguments = parseArgumentList();
|
|
expression = finishNode(callExpr);
|
|
continue;
|
|
}
|
|
else if (token === SyntaxKind.OpenParenToken) {
|
|
let callExpr = <CallExpression>createNode(SyntaxKind.CallExpression, expression.pos);
|
|
callExpr.expression = expression;
|
|
callExpr.arguments = parseArgumentList();
|
|
expression = finishNode(callExpr);
|
|
continue;
|
|
}
|
|
|
|
return expression;
|
|
}
|
|
}
|
|
|
|
function parseArgumentList() {
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
let result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
return result;
|
|
}
|
|
|
|
function parseTypeArgumentsInExpression() {
|
|
if (!parseOptional(SyntaxKind.LessThanToken)) {
|
|
return undefined;
|
|
}
|
|
|
|
let typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType);
|
|
if (!parseExpected(SyntaxKind.GreaterThanToken)) {
|
|
// If it doesn't have the closing > then it's definitely not an type argument list.
|
|
return undefined;
|
|
}
|
|
|
|
// If we have a '<', then only parse this as a arugment list if the type arguments
|
|
// are complete and we have an open paren. if we don't, rewind and return nothing.
|
|
return typeArguments && canFollowTypeArgumentsInExpression()
|
|
? typeArguments
|
|
: undefined;
|
|
}
|
|
|
|
function canFollowTypeArgumentsInExpression(): boolean {
|
|
switch (token) {
|
|
case SyntaxKind.OpenParenToken: // foo<x>(
|
|
// this case are the only case where this token can legally follow a type argument
|
|
// list. So we definitely want to treat this as a type arg list.
|
|
|
|
case SyntaxKind.DotToken: // foo<x>.
|
|
case SyntaxKind.CloseParenToken: // foo<x>)
|
|
case SyntaxKind.CloseBracketToken: // foo<x>]
|
|
case SyntaxKind.ColonToken: // foo<x>:
|
|
case SyntaxKind.SemicolonToken: // foo<x>;
|
|
case SyntaxKind.QuestionToken: // foo<x>?
|
|
case SyntaxKind.EqualsEqualsToken: // foo<x> ==
|
|
case SyntaxKind.EqualsEqualsEqualsToken: // foo<x> ===
|
|
case SyntaxKind.ExclamationEqualsToken: // foo<x> !=
|
|
case SyntaxKind.ExclamationEqualsEqualsToken: // foo<x> !==
|
|
case SyntaxKind.AmpersandAmpersandToken: // foo<x> &&
|
|
case SyntaxKind.BarBarToken: // foo<x> ||
|
|
case SyntaxKind.CaretToken: // foo<x> ^
|
|
case SyntaxKind.AmpersandToken: // foo<x> &
|
|
case SyntaxKind.BarToken: // foo<x> |
|
|
case SyntaxKind.CloseBraceToken: // foo<x> }
|
|
case SyntaxKind.EndOfFileToken: // foo<x>
|
|
// these cases can't legally follow a type arg list. However, they're not legal
|
|
// expressions either. The user is probably in the middle of a generic type. So
|
|
// treat it as such.
|
|
return true;
|
|
|
|
case SyntaxKind.CommaToken: // foo<x>,
|
|
case SyntaxKind.OpenBraceToken: // foo<x> {
|
|
// We don't want to treat these as type arguments. Otherwise we'll parse this
|
|
// as an invocation expression. Instead, we want to parse out the expression
|
|
// in isolation from the type arguments.
|
|
|
|
default:
|
|
// Anything else treat as an expression.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function parsePrimaryExpression(): PrimaryExpression {
|
|
switch (token) {
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
return parseLiteralNode();
|
|
case SyntaxKind.ThisKeyword:
|
|
case SyntaxKind.SuperKeyword:
|
|
case SyntaxKind.NullKeyword:
|
|
case SyntaxKind.TrueKeyword:
|
|
case SyntaxKind.FalseKeyword:
|
|
return parseTokenNode<PrimaryExpression>();
|
|
case SyntaxKind.OpenParenToken:
|
|
return parseParenthesizedExpression();
|
|
case SyntaxKind.OpenBracketToken:
|
|
return parseArrayLiteralExpression();
|
|
case SyntaxKind.OpenBraceToken:
|
|
return parseObjectLiteralExpression();
|
|
case SyntaxKind.ClassKeyword:
|
|
return parseClassExpression();
|
|
case SyntaxKind.FunctionKeyword:
|
|
return parseFunctionExpression();
|
|
case SyntaxKind.NewKeyword:
|
|
return parseNewExpression();
|
|
case SyntaxKind.SlashToken:
|
|
case SyntaxKind.SlashEqualsToken:
|
|
if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) {
|
|
return parseLiteralNode();
|
|
}
|
|
break;
|
|
case SyntaxKind.TemplateHead:
|
|
return parseTemplateExpression();
|
|
}
|
|
|
|
return parseIdentifier(Diagnostics.Expression_expected);
|
|
}
|
|
|
|
function parseParenthesizedExpression(): ParenthesizedExpression {
|
|
let node = <ParenthesizedExpression>createNode(SyntaxKind.ParenthesizedExpression);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseSpreadElement(): Expression {
|
|
let node = <SpreadElementExpression>createNode(SyntaxKind.SpreadElementExpression);
|
|
parseExpected(SyntaxKind.DotDotDotToken);
|
|
node.expression = parseAssignmentExpressionOrHigher();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseArgumentOrArrayLiteralElement(): Expression {
|
|
return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() :
|
|
token === SyntaxKind.CommaToken ? <Expression>createNode(SyntaxKind.OmittedExpression) :
|
|
parseAssignmentExpressionOrHigher();
|
|
}
|
|
|
|
function parseArgumentExpression(): Expression {
|
|
return doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement);
|
|
}
|
|
|
|
function parseArrayLiteralExpression(): ArrayLiteralExpression {
|
|
let node = <ArrayLiteralExpression>createNode(SyntaxKind.ArrayLiteralExpression);
|
|
parseExpected(SyntaxKind.OpenBracketToken);
|
|
if (scanner.hasPrecedingLineBreak()) node.flags |= NodeFlags.MultiLine;
|
|
node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement);
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function tryParseAccessorDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): AccessorDeclaration {
|
|
if (parseContextualModifier(SyntaxKind.GetKeyword)) {
|
|
return parseAccessorDeclaration(SyntaxKind.GetAccessor, fullStart, decorators, modifiers);
|
|
}
|
|
else if (parseContextualModifier(SyntaxKind.SetKeyword)) {
|
|
return parseAccessorDeclaration(SyntaxKind.SetAccessor, fullStart, decorators, modifiers);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function parseObjectLiteralElement(): ObjectLiteralElement {
|
|
let fullStart = scanner.getStartPos();
|
|
let decorators = parseDecorators();
|
|
let modifiers = parseModifiers();
|
|
|
|
let accessor = tryParseAccessorDeclaration(fullStart, decorators, modifiers);
|
|
if (accessor) {
|
|
return accessor;
|
|
}
|
|
|
|
let asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
|
|
let tokenIsIdentifier = isIdentifier();
|
|
let nameToken = token;
|
|
let propertyName = parsePropertyName();
|
|
|
|
// Disallowing of optional property assignments happens in the grammar checker.
|
|
let questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
|
|
if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
|
|
return parseMethodDeclaration(fullStart, decorators, modifiers, asteriskToken, propertyName, questionToken);
|
|
}
|
|
|
|
// Parse to check if it is short-hand property assignment or normal property assignment
|
|
if ((token === SyntaxKind.CommaToken || token === SyntaxKind.CloseBraceToken) && tokenIsIdentifier) {
|
|
let shorthandDeclaration = <ShorthandPropertyAssignment>createNode(SyntaxKind.ShorthandPropertyAssignment, fullStart);
|
|
shorthandDeclaration.name = <Identifier>propertyName;
|
|
shorthandDeclaration.questionToken = questionToken;
|
|
return finishNode(shorthandDeclaration);
|
|
}
|
|
else {
|
|
let propertyAssignment = <PropertyAssignment>createNode(SyntaxKind.PropertyAssignment, fullStart);
|
|
propertyAssignment.name = propertyName;
|
|
propertyAssignment.questionToken = questionToken;
|
|
parseExpected(SyntaxKind.ColonToken);
|
|
propertyAssignment.initializer = allowInAnd(parseAssignmentExpressionOrHigher);
|
|
return finishNode(propertyAssignment);
|
|
}
|
|
}
|
|
|
|
function parseObjectLiteralExpression(): ObjectLiteralExpression {
|
|
let node = <ObjectLiteralExpression>createNode(SyntaxKind.ObjectLiteralExpression);
|
|
parseExpected(SyntaxKind.OpenBraceToken);
|
|
if (scanner.hasPrecedingLineBreak()) {
|
|
node.flags |= NodeFlags.MultiLine;
|
|
}
|
|
|
|
node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimeter*/ true);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseFunctionExpression(): FunctionExpression {
|
|
// GeneratorExpression :
|
|
// function * BindingIdentifier[Yield]opt (FormalParameters[Yield, GeneratorParameter]) { GeneratorBody[Yield] }
|
|
// FunctionExpression:
|
|
// function BindingIdentifieropt(FormalParameters) { FunctionBody }
|
|
let saveDecoratorContext = inDecoratorContext();
|
|
if (saveDecoratorContext) {
|
|
setDecoratorContext(false);
|
|
}
|
|
let node = <FunctionExpression>createNode(SyntaxKind.FunctionExpression);
|
|
parseExpected(SyntaxKind.FunctionKeyword);
|
|
node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
|
|
node.name = node.asteriskToken ? doInYieldContext(parseOptionalIdentifier) : parseOptionalIdentifier();
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ !!node.asteriskToken, /*requireCompleteParameterList*/ false, node);
|
|
node.body = parseFunctionBlock(/*allowYield*/ !!node.asteriskToken, /* ignoreMissingOpenBrace */ false);
|
|
if (saveDecoratorContext) {
|
|
setDecoratorContext(true);
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseOptionalIdentifier() {
|
|
return isIdentifier() ? parseIdentifier() : undefined;
|
|
}
|
|
|
|
function parseNewExpression(): NewExpression {
|
|
let node = <NewExpression>createNode(SyntaxKind.NewExpression);
|
|
parseExpected(SyntaxKind.NewKeyword);
|
|
node.expression = parseMemberExpressionOrHigher();
|
|
node.typeArguments = tryParse(parseTypeArgumentsInExpression);
|
|
if (node.typeArguments || token === SyntaxKind.OpenParenToken) {
|
|
node.arguments = parseArgumentList();
|
|
}
|
|
|
|
return finishNode(node);
|
|
}
|
|
|
|
// STATEMENTS
|
|
function parseBlock(ignoreMissingOpenBrace: boolean, checkForStrictMode: boolean, diagnosticMessage?: DiagnosticMessage): Block {
|
|
let node = <Block>createNode(SyntaxKind.Block);
|
|
if (parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage) || ignoreMissingOpenBrace) {
|
|
node.statements = parseList(ParsingContext.BlockStatements, checkForStrictMode, parseStatement);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
}
|
|
else {
|
|
node.statements = createMissingList<Statement>();
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseFunctionBlock(allowYield: boolean, ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block {
|
|
let savedYieldContext = inYieldContext();
|
|
setYieldContext(allowYield);
|
|
|
|
// We may be in a [Decorator] context when parsing a function expression or
|
|
// arrow function. The body of the function is not in [Decorator] context.
|
|
let saveDecoratorContext = inDecoratorContext();
|
|
if (saveDecoratorContext) {
|
|
setDecoratorContext(false);
|
|
}
|
|
|
|
let block = parseBlock(ignoreMissingOpenBrace, /*checkForStrictMode*/ true, diagnosticMessage);
|
|
|
|
if (saveDecoratorContext) {
|
|
setDecoratorContext(true);
|
|
}
|
|
|
|
setYieldContext(savedYieldContext);
|
|
|
|
return block;
|
|
}
|
|
|
|
function parseEmptyStatement(): Statement {
|
|
let node = <Statement>createNode(SyntaxKind.EmptyStatement);
|
|
parseExpected(SyntaxKind.SemicolonToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseIfStatement(): IfStatement {
|
|
let node = <IfStatement>createNode(SyntaxKind.IfStatement);
|
|
parseExpected(SyntaxKind.IfKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
node.thenStatement = parseStatement();
|
|
node.elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined;
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseDoStatement(): DoStatement {
|
|
let node = <DoStatement>createNode(SyntaxKind.DoStatement);
|
|
parseExpected(SyntaxKind.DoKeyword);
|
|
node.statement = parseStatement();
|
|
parseExpected(SyntaxKind.WhileKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
|
|
// From: https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html
|
|
// 157 min --- All allen at wirfs-brock.com CONF --- "do{;}while(false)false" prohibited in
|
|
// spec but allowed in consensus reality. Approved -- this is the de-facto standard whereby
|
|
// do;while(0)x will have a semicolon inserted before x.
|
|
parseOptional(SyntaxKind.SemicolonToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseWhileStatement(): WhileStatement {
|
|
let node = <WhileStatement>createNode(SyntaxKind.WhileStatement);
|
|
parseExpected(SyntaxKind.WhileKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
node.statement = parseStatement();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseForOrForInOrForOfStatement(): Statement {
|
|
let pos = getNodePos();
|
|
parseExpected(SyntaxKind.ForKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
|
|
let initializer: VariableDeclarationList | Expression = undefined;
|
|
if (token !== SyntaxKind.SemicolonToken) {
|
|
if (token === SyntaxKind.VarKeyword || token === SyntaxKind.LetKeyword || token === SyntaxKind.ConstKeyword) {
|
|
initializer = parseVariableDeclarationList(/*inForStatementInitializer*/ true);
|
|
}
|
|
else {
|
|
initializer = disallowInAnd(parseExpression);
|
|
}
|
|
}
|
|
let forOrForInOrForOfStatement: IterationStatement;
|
|
if (parseOptional(SyntaxKind.InKeyword)) {
|
|
let forInStatement = <ForInStatement>createNode(SyntaxKind.ForInStatement, pos);
|
|
forInStatement.initializer = initializer;
|
|
forInStatement.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
forOrForInOrForOfStatement = forInStatement;
|
|
}
|
|
else if (parseOptional(SyntaxKind.OfKeyword)) {
|
|
let forOfStatement = <ForOfStatement>createNode(SyntaxKind.ForOfStatement, pos);
|
|
forOfStatement.initializer = initializer;
|
|
forOfStatement.expression = allowInAnd(parseAssignmentExpressionOrHigher);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
forOrForInOrForOfStatement = forOfStatement;
|
|
} else {
|
|
let forStatement = <ForStatement>createNode(SyntaxKind.ForStatement, pos);
|
|
forStatement.initializer = initializer;
|
|
parseExpected(SyntaxKind.SemicolonToken);
|
|
if (token !== SyntaxKind.SemicolonToken && token !== SyntaxKind.CloseParenToken) {
|
|
forStatement.condition = allowInAnd(parseExpression);
|
|
}
|
|
parseExpected(SyntaxKind.SemicolonToken);
|
|
if (token !== SyntaxKind.CloseParenToken) {
|
|
forStatement.incrementor = allowInAnd(parseExpression);
|
|
}
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
forOrForInOrForOfStatement = forStatement;
|
|
}
|
|
|
|
forOrForInOrForOfStatement.statement = parseStatement();
|
|
|
|
return finishNode(forOrForInOrForOfStatement);
|
|
}
|
|
|
|
function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement {
|
|
let node = <BreakOrContinueStatement>createNode(kind);
|
|
|
|
parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword);
|
|
if (!canParseSemicolon()) {
|
|
node.label = parseIdentifier();
|
|
}
|
|
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseReturnStatement(): ReturnStatement {
|
|
let node = <ReturnStatement>createNode(SyntaxKind.ReturnStatement);
|
|
|
|
parseExpected(SyntaxKind.ReturnKeyword);
|
|
if (!canParseSemicolon()) {
|
|
node.expression = allowInAnd(parseExpression);
|
|
}
|
|
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseWithStatement(): WithStatement {
|
|
let node = <WithStatement>createNode(SyntaxKind.WithStatement);
|
|
parseExpected(SyntaxKind.WithKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
node.statement = parseStatement();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseCaseClause(): CaseClause {
|
|
let node = <CaseClause>createNode(SyntaxKind.CaseClause);
|
|
parseExpected(SyntaxKind.CaseKeyword);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.ColonToken);
|
|
node.statements = parseList(ParsingContext.SwitchClauseStatements, /*checkForStrictMode*/ false, parseStatement);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseDefaultClause(): DefaultClause {
|
|
let node = <DefaultClause>createNode(SyntaxKind.DefaultClause);
|
|
parseExpected(SyntaxKind.DefaultKeyword);
|
|
parseExpected(SyntaxKind.ColonToken);
|
|
node.statements = parseList(ParsingContext.SwitchClauseStatements, /*checkForStrictMode*/ false, parseStatement);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseCaseOrDefaultClause(): CaseOrDefaultClause {
|
|
return token === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause();
|
|
}
|
|
|
|
function parseSwitchStatement(): SwitchStatement {
|
|
let node = <SwitchStatement>createNode(SyntaxKind.SwitchStatement);
|
|
parseExpected(SyntaxKind.SwitchKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = allowInAnd(parseExpression);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
let caseBlock = <CaseBlock>createNode(SyntaxKind.CaseBlock, scanner.getStartPos());
|
|
parseExpected(SyntaxKind.OpenBraceToken);
|
|
caseBlock.clauses = parseList(ParsingContext.SwitchClauses, /*checkForStrictMode*/ false, parseCaseOrDefaultClause);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
node.caseBlock = finishNode(caseBlock);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseThrowStatement(): ThrowStatement {
|
|
// ThrowStatement[Yield] :
|
|
// throw [no LineTerminator here]Expression[In, ?Yield];
|
|
|
|
// Because of automatic semicolon insertion, we need to report error if this
|
|
// throw could be terminated with a semicolon. Note: we can't call 'parseExpression'
|
|
// directly as that might consume an expression on the following line.
|
|
// We just return 'undefined' in that case. The actual error will be reported in the
|
|
// grammar walker.
|
|
let node = <ThrowStatement>createNode(SyntaxKind.ThrowStatement);
|
|
parseExpected(SyntaxKind.ThrowKeyword);
|
|
node.expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression);
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
// TODO: Review for error recovery
|
|
function parseTryStatement(): TryStatement {
|
|
let node = <TryStatement>createNode(SyntaxKind.TryStatement);
|
|
|
|
parseExpected(SyntaxKind.TryKeyword);
|
|
node.tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false, /*checkForStrictMode*/ false);
|
|
node.catchClause = token === SyntaxKind.CatchKeyword ? parseCatchClause() : undefined;
|
|
|
|
// If we don't have a catch clause, then we must have a finally clause. Try to parse
|
|
// one out no matter what.
|
|
if (!node.catchClause || token === SyntaxKind.FinallyKeyword) {
|
|
parseExpected(SyntaxKind.FinallyKeyword);
|
|
node.finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false, /*checkForStrictMode*/ false);
|
|
}
|
|
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseCatchClause(): CatchClause {
|
|
let result = <CatchClause>createNode(SyntaxKind.CatchClause);
|
|
parseExpected(SyntaxKind.CatchKeyword);
|
|
if (parseExpected(SyntaxKind.OpenParenToken)) {
|
|
result.variableDeclaration = parseVariableDeclaration();
|
|
}
|
|
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
result.block = parseBlock(/*ignoreMissingOpenBrace*/ false, /*checkForStrictMode*/ false);
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseDebuggerStatement(): Statement {
|
|
let node = <Statement>createNode(SyntaxKind.DebuggerStatement);
|
|
parseExpected(SyntaxKind.DebuggerKeyword);
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseExpressionOrLabeledStatement(): ExpressionStatement | LabeledStatement {
|
|
// Avoiding having to do the lookahead for a labeled statement by just trying to parse
|
|
// out an expression, seeing if it is identifier and then seeing if it is followed by
|
|
// a colon.
|
|
let fullStart = scanner.getStartPos();
|
|
let expression = allowInAnd(parseExpression);
|
|
|
|
if (expression.kind === SyntaxKind.Identifier && parseOptional(SyntaxKind.ColonToken)) {
|
|
let labeledStatement = <LabeledStatement>createNode(SyntaxKind.LabeledStatement, fullStart);
|
|
labeledStatement.label = <Identifier>expression;
|
|
labeledStatement.statement = parseStatement();
|
|
return finishNode(labeledStatement);
|
|
}
|
|
else {
|
|
let expressionStatement = <ExpressionStatement>createNode(SyntaxKind.ExpressionStatement, fullStart);
|
|
expressionStatement.expression = expression;
|
|
parseSemicolon();
|
|
return finishNode(expressionStatement);
|
|
}
|
|
}
|
|
|
|
function isIdentifierOrKeyword() {
|
|
return token >= SyntaxKind.Identifier;
|
|
}
|
|
|
|
function nextTokenIsIdentifierOrKeywordOnSameLine() {
|
|
nextToken();
|
|
return isIdentifierOrKeyword() && !scanner.hasPrecedingLineBreak();
|
|
}
|
|
|
|
function parseDeclarationFlags(): StatementFlags {
|
|
while (true) {
|
|
switch (token) {
|
|
case SyntaxKind.VarKeyword:
|
|
case SyntaxKind.LetKeyword:
|
|
case SyntaxKind.ConstKeyword:
|
|
case SyntaxKind.FunctionKeyword:
|
|
case SyntaxKind.ClassKeyword:
|
|
case SyntaxKind.EnumKeyword:
|
|
return StatementFlags.Statement;
|
|
case SyntaxKind.InterfaceKeyword:
|
|
case SyntaxKind.TypeKeyword:
|
|
nextToken();
|
|
return isIdentifierOrKeyword() ? StatementFlags.Statement : StatementFlags.None;
|
|
case SyntaxKind.ModuleKeyword:
|
|
case SyntaxKind.NamespaceKeyword:
|
|
nextToken();
|
|
return isIdentifierOrKeyword() || token === SyntaxKind.StringLiteral ? StatementFlags.ModuleElement : StatementFlags.None;
|
|
case SyntaxKind.ImportKeyword:
|
|
nextToken();
|
|
return token === SyntaxKind.StringLiteral || token === SyntaxKind.AsteriskToken ||
|
|
token === SyntaxKind.OpenBraceToken || isIdentifierOrKeyword() ?
|
|
StatementFlags.ModuleElement : StatementFlags.None;
|
|
case SyntaxKind.ExportKeyword:
|
|
nextToken();
|
|
if (token === SyntaxKind.EqualsToken || token === SyntaxKind.AsteriskToken ||
|
|
token === SyntaxKind.OpenBraceToken || token === SyntaxKind.DefaultKeyword) {
|
|
return StatementFlags.ModuleElement;
|
|
}
|
|
continue;
|
|
case SyntaxKind.DeclareKeyword:
|
|
case SyntaxKind.PublicKeyword:
|
|
case SyntaxKind.PrivateKeyword:
|
|
case SyntaxKind.ProtectedKeyword:
|
|
case SyntaxKind.StaticKeyword:
|
|
nextToken();
|
|
continue;
|
|
default:
|
|
return StatementFlags.None;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getDeclarationFlags(): StatementFlags {
|
|
return lookAhead(parseDeclarationFlags);
|
|
}
|
|
|
|
function getStatementFlags(): StatementFlags {
|
|
switch (token) {
|
|
case SyntaxKind.AtToken:
|
|
case SyntaxKind.SemicolonToken:
|
|
case SyntaxKind.OpenBraceToken:
|
|
case SyntaxKind.VarKeyword:
|
|
case SyntaxKind.LetKeyword:
|
|
case SyntaxKind.FunctionKeyword:
|
|
case SyntaxKind.ClassKeyword:
|
|
case SyntaxKind.EnumKeyword:
|
|
case SyntaxKind.IfKeyword:
|
|
case SyntaxKind.DoKeyword:
|
|
case SyntaxKind.WhileKeyword:
|
|
case SyntaxKind.ForKeyword:
|
|
case SyntaxKind.ContinueKeyword:
|
|
case SyntaxKind.BreakKeyword:
|
|
case SyntaxKind.ReturnKeyword:
|
|
case SyntaxKind.WithKeyword:
|
|
case SyntaxKind.SwitchKeyword:
|
|
case SyntaxKind.ThrowKeyword:
|
|
case SyntaxKind.TryKeyword:
|
|
case SyntaxKind.DebuggerKeyword:
|
|
// 'catch' and 'finally' do not actually indicate that the code is part of a statement,
|
|
// however, we say they are here so that we may gracefully parse them and error later.
|
|
case SyntaxKind.CatchKeyword:
|
|
case SyntaxKind.FinallyKeyword:
|
|
return StatementFlags.Statement;
|
|
|
|
case SyntaxKind.ConstKeyword:
|
|
case SyntaxKind.ExportKeyword:
|
|
case SyntaxKind.ImportKeyword:
|
|
return getDeclarationFlags();
|
|
|
|
case SyntaxKind.DeclareKeyword:
|
|
case SyntaxKind.InterfaceKeyword:
|
|
case SyntaxKind.ModuleKeyword:
|
|
case SyntaxKind.NamespaceKeyword:
|
|
case SyntaxKind.TypeKeyword:
|
|
// When these don't start a declaration, they're an identifier in an expression statement
|
|
return getDeclarationFlags() || StatementFlags.Statement;
|
|
|
|
case SyntaxKind.PublicKeyword:
|
|
case SyntaxKind.PrivateKeyword:
|
|
case SyntaxKind.ProtectedKeyword:
|
|
case SyntaxKind.StaticKeyword:
|
|
// When these don't start a declaration, they may be the start of a class member if an identifier
|
|
// immediately follows. Otherwise they're an identifier in an expression statement.
|
|
return getDeclarationFlags() ||
|
|
(lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? StatementFlags.None : StatementFlags.Statement);
|
|
|
|
default:
|
|
return isStartOfExpression() ? StatementFlags.Statement : StatementFlags.None;
|
|
}
|
|
}
|
|
|
|
function isStartOfStatement(): boolean {
|
|
return (getStatementFlags() & StatementFlags.Statement) !== 0;
|
|
}
|
|
|
|
function isStartOfModuleElement(): boolean {
|
|
return (getStatementFlags() & StatementFlags.StatementOrModuleElement) !== 0;
|
|
}
|
|
|
|
function nextTokenIsIdentifierOrStartOfDestructuringOnTheSameLine() {
|
|
nextToken();
|
|
return !scanner.hasPrecedingLineBreak() &&
|
|
(isIdentifier() || token === SyntaxKind.OpenBraceToken || token === SyntaxKind.OpenBracketToken);
|
|
}
|
|
|
|
function isLetDeclaration() {
|
|
// It is let declaration if in strict mode or next token is identifier\open bracket\open curly on same line.
|
|
// otherwise it needs to be treated like identifier
|
|
return inStrictModeContext() || lookAhead(nextTokenIsIdentifierOrStartOfDestructuringOnTheSameLine);
|
|
}
|
|
|
|
function parseStatement(): Statement {
|
|
return <Statement>parseModuleElementOfKind(StatementFlags.Statement);
|
|
}
|
|
|
|
function parseModuleElement(): ModuleElement {
|
|
return parseModuleElementOfKind(StatementFlags.StatementOrModuleElement);
|
|
}
|
|
|
|
function parseSourceElement(): ModuleElement {
|
|
return parseModuleElementOfKind(StatementFlags.StatementOrModuleElement);
|
|
}
|
|
|
|
function parseModuleElementOfKind(flags: StatementFlags): ModuleElement {
|
|
switch (token) {
|
|
case SyntaxKind.SemicolonToken:
|
|
return parseEmptyStatement();
|
|
case SyntaxKind.OpenBraceToken:
|
|
return parseBlock(/*ignoreMissingOpenBrace*/ false, /*checkForStrictMode*/ false);
|
|
case SyntaxKind.VarKeyword:
|
|
return parseVariableStatement(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined);
|
|
case SyntaxKind.LetKeyword:
|
|
if (isLetDeclaration()) {
|
|
return parseVariableStatement(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined);
|
|
}
|
|
break;
|
|
case SyntaxKind.FunctionKeyword:
|
|
return parseFunctionDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined);
|
|
case SyntaxKind.ClassKeyword:
|
|
return parseClassDeclaration(scanner.getStartPos(), /*decorators*/ undefined, /*modifiers*/ undefined);
|
|
case SyntaxKind.IfKeyword:
|
|
return parseIfStatement();
|
|
case SyntaxKind.DoKeyword:
|
|
return parseDoStatement();
|
|
case SyntaxKind.WhileKeyword:
|
|
return parseWhileStatement();
|
|
case SyntaxKind.ForKeyword:
|
|
return parseForOrForInOrForOfStatement();
|
|
case SyntaxKind.ContinueKeyword:
|
|
return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement);
|
|
case SyntaxKind.BreakKeyword:
|
|
return parseBreakOrContinueStatement(SyntaxKind.BreakStatement);
|
|
case SyntaxKind.ReturnKeyword:
|
|
return parseReturnStatement();
|
|
case SyntaxKind.WithKeyword:
|
|
return parseWithStatement();
|
|
case SyntaxKind.SwitchKeyword:
|
|
return parseSwitchStatement();
|
|
case SyntaxKind.ThrowKeyword:
|
|
return parseThrowStatement();
|
|
case SyntaxKind.TryKeyword:
|
|
// Include the next two for error recovery.
|
|
case SyntaxKind.CatchKeyword:
|
|
case SyntaxKind.FinallyKeyword:
|
|
return parseTryStatement();
|
|
case SyntaxKind.DebuggerKeyword:
|
|
return parseDebuggerStatement();
|
|
case SyntaxKind.AtToken:
|
|
return parseDeclaration();
|
|
case SyntaxKind.ConstKeyword:
|
|
case SyntaxKind.DeclareKeyword:
|
|
case SyntaxKind.EnumKeyword:
|
|
case SyntaxKind.ExportKeyword:
|
|
case SyntaxKind.ImportKeyword:
|
|
case SyntaxKind.InterfaceKeyword:
|
|
case SyntaxKind.ModuleKeyword:
|
|
case SyntaxKind.NamespaceKeyword:
|
|
case SyntaxKind.PrivateKeyword:
|
|
case SyntaxKind.ProtectedKeyword:
|
|
case SyntaxKind.PublicKeyword:
|
|
case SyntaxKind.StaticKeyword:
|
|
case SyntaxKind.TypeKeyword:
|
|
if (getDeclarationFlags() & flags) {
|
|
return parseDeclaration();
|
|
}
|
|
break;
|
|
}
|
|
return parseExpressionOrLabeledStatement();
|
|
}
|
|
|
|
function parseDeclaration(): ModuleElement {
|
|
let fullStart = getNodePos();
|
|
let decorators = parseDecorators();
|
|
let modifiers = parseModifiers();
|
|
switch (token) {
|
|
case SyntaxKind.VarKeyword:
|
|
case SyntaxKind.LetKeyword:
|
|
case SyntaxKind.ConstKeyword:
|
|
return parseVariableStatement(fullStart, decorators, modifiers);
|
|
case SyntaxKind.FunctionKeyword:
|
|
return parseFunctionDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.ClassKeyword:
|
|
return parseClassDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.InterfaceKeyword:
|
|
return parseInterfaceDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.TypeKeyword:
|
|
return parseTypeAliasDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.EnumKeyword:
|
|
return parseEnumDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.ModuleKeyword:
|
|
case SyntaxKind.NamespaceKeyword:
|
|
return parseModuleDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.ImportKeyword:
|
|
return parseImportDeclarationOrImportEqualsDeclaration(fullStart, decorators, modifiers);
|
|
case SyntaxKind.ExportKeyword:
|
|
nextToken();
|
|
return token === SyntaxKind.DefaultKeyword || token === SyntaxKind.EqualsToken ?
|
|
parseExportAssignment(fullStart, decorators, modifiers) :
|
|
parseExportDeclaration(fullStart, decorators, modifiers);
|
|
default:
|
|
if (decorators) {
|
|
// We reached this point because we encountered decorators and/or modifiers and assumed a declaration
|
|
// would follow. For recovery and error reporting purposes, return an incomplete declaration.
|
|
let node = <ModuleElement>createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected);
|
|
node.pos = fullStart;
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
return finishNode(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function parseFunctionBlockOrSemicolon(isGenerator: boolean, diagnosticMessage?: DiagnosticMessage): Block {
|
|
if (token !== SyntaxKind.OpenBraceToken && canParseSemicolon()) {
|
|
parseSemicolon();
|
|
return;
|
|
}
|
|
|
|
return parseFunctionBlock(isGenerator, /*ignoreMissingOpenBrace*/ false, diagnosticMessage);
|
|
}
|
|
|
|
// DECLARATIONS
|
|
|
|
function parseArrayBindingElement(): BindingElement {
|
|
if (token === SyntaxKind.CommaToken) {
|
|
return <BindingElement>createNode(SyntaxKind.OmittedExpression);
|
|
}
|
|
let node = <BindingElement>createNode(SyntaxKind.BindingElement);
|
|
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
|
|
node.name = parseIdentifierOrPattern();
|
|
node.initializer = parseInitializer(/*inParameter*/ false);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseObjectBindingElement(): BindingElement {
|
|
let node = <BindingElement>createNode(SyntaxKind.BindingElement);
|
|
// TODO(andersh): Handle computed properties
|
|
let tokenIsIdentifier = isIdentifier();
|
|
let propertyName = parsePropertyName();
|
|
if (tokenIsIdentifier && token !== SyntaxKind.ColonToken) {
|
|
node.name = <Identifier>propertyName;
|
|
}
|
|
else {
|
|
parseExpected(SyntaxKind.ColonToken);
|
|
node.propertyName = <Identifier>propertyName;
|
|
node.name = parseIdentifierOrPattern();
|
|
}
|
|
node.initializer = parseInitializer(/*inParameter*/ false);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseObjectBindingPattern(): BindingPattern {
|
|
let node = <BindingPattern>createNode(SyntaxKind.ObjectBindingPattern);
|
|
parseExpected(SyntaxKind.OpenBraceToken);
|
|
node.elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseArrayBindingPattern(): BindingPattern {
|
|
let node = <BindingPattern>createNode(SyntaxKind.ArrayBindingPattern);
|
|
parseExpected(SyntaxKind.OpenBracketToken);
|
|
node.elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement);
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function isIdentifierOrPattern() {
|
|
return token === SyntaxKind.OpenBraceToken || token === SyntaxKind.OpenBracketToken || isIdentifier();
|
|
}
|
|
|
|
function parseIdentifierOrPattern(): Identifier | BindingPattern {
|
|
if (token === SyntaxKind.OpenBracketToken) {
|
|
return parseArrayBindingPattern();
|
|
}
|
|
if (token === SyntaxKind.OpenBraceToken) {
|
|
return parseObjectBindingPattern();
|
|
}
|
|
return parseIdentifier();
|
|
}
|
|
|
|
function parseVariableDeclaration(): VariableDeclaration {
|
|
let node = <VariableDeclaration>createNode(SyntaxKind.VariableDeclaration);
|
|
node.name = parseIdentifierOrPattern();
|
|
node.type = parseTypeAnnotation();
|
|
if (!isInOrOfKeyword(token)) {
|
|
node.initializer = parseInitializer(/*inParameter*/ false);
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList {
|
|
let node = <VariableDeclarationList>createNode(SyntaxKind.VariableDeclarationList);
|
|
|
|
switch (token) {
|
|
case SyntaxKind.VarKeyword:
|
|
break;
|
|
case SyntaxKind.LetKeyword:
|
|
node.flags |= NodeFlags.Let;
|
|
break;
|
|
case SyntaxKind.ConstKeyword:
|
|
node.flags |= NodeFlags.Const;
|
|
break;
|
|
default:
|
|
Debug.fail();
|
|
}
|
|
|
|
nextToken();
|
|
|
|
// The user may have written the following:
|
|
//
|
|
// for (let of X) { }
|
|
//
|
|
// In this case, we want to parse an empty declaration list, and then parse 'of'
|
|
// as a keyword. The reason this is not automatic is that 'of' is a valid identifier.
|
|
// So we need to look ahead to determine if 'of' should be treated as a keyword in
|
|
// this context.
|
|
// The checker will then give an error that there is an empty declaration list.
|
|
if (token === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) {
|
|
node.declarations = createMissingList<VariableDeclaration>();
|
|
}
|
|
else {
|
|
let savedDisallowIn = inDisallowInContext();
|
|
setDisallowInContext(inForStatementInitializer);
|
|
|
|
node.declarations = parseDelimitedList(ParsingContext.VariableDeclarations, parseVariableDeclaration);
|
|
|
|
setDisallowInContext(savedDisallowIn);
|
|
}
|
|
|
|
return finishNode(node);
|
|
}
|
|
|
|
function canFollowContextualOfKeyword(): boolean {
|
|
return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken;
|
|
}
|
|
|
|
function parseVariableStatement(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): VariableStatement {
|
|
let node = <VariableStatement>createNode(SyntaxKind.VariableStatement, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
node.declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false);
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseFunctionDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): FunctionDeclaration {
|
|
let node = <FunctionDeclaration>createNode(SyntaxKind.FunctionDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
parseExpected(SyntaxKind.FunctionKeyword);
|
|
node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
|
|
node.name = node.flags & NodeFlags.Default ? parseOptionalIdentifier() : parseIdentifier();
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ !!node.asteriskToken, /*requireCompleteParameterList*/ false, node);
|
|
node.body = parseFunctionBlockOrSemicolon(!!node.asteriskToken, Diagnostics.or_expected);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseConstructorDeclaration(pos: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ConstructorDeclaration {
|
|
let node = <ConstructorDeclaration>createNode(SyntaxKind.Constructor, pos);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
parseExpected(SyntaxKind.ConstructorKeyword);
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ false, node);
|
|
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false, Diagnostics.or_expected);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseMethodDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray, asteriskToken: Node, name: DeclarationName, questionToken: Node, diagnosticMessage?: DiagnosticMessage): MethodDeclaration {
|
|
let method = <MethodDeclaration>createNode(SyntaxKind.MethodDeclaration, fullStart);
|
|
method.decorators = decorators;
|
|
setModifiers(method, modifiers);
|
|
method.asteriskToken = asteriskToken;
|
|
method.name = name;
|
|
method.questionToken = questionToken;
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ !!asteriskToken, /*requireCompleteParameterList*/ false, method);
|
|
method.body = parseFunctionBlockOrSemicolon(!!asteriskToken, diagnosticMessage);
|
|
return finishNode(method);
|
|
}
|
|
|
|
function parsePropertyDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray, name: DeclarationName, questionToken: Node): ClassElement {
|
|
let property = <PropertyDeclaration>createNode(SyntaxKind.PropertyDeclaration, fullStart);
|
|
property.decorators = decorators;
|
|
setModifiers(property, modifiers);
|
|
property.name = name;
|
|
property.questionToken = questionToken;
|
|
property.type = parseTypeAnnotation();
|
|
|
|
// For instance properties specifically, since they are evaluated inside the constructor,
|
|
// we do *not * want to parse yield expressions, so we specifically turn the yield context
|
|
// off. The grammar would look something like this:
|
|
//
|
|
// MemberVariableDeclaration[Yield]:
|
|
// AccessibilityModifier_opt PropertyName TypeAnnotation_opt Initialiser_opt[In];
|
|
// AccessibilityModifier_opt static_opt PropertyName TypeAnnotation_opt Initialiser_opt[In, ?Yield];
|
|
//
|
|
// The checker may still error in the static case to explicitly disallow the yield expression.
|
|
property.initializer = modifiers && modifiers.flags & NodeFlags.Static
|
|
? allowInAnd(parseNonParameterInitializer)
|
|
: doOutsideOfContext(ParserContextFlags.Yield | ParserContextFlags.DisallowIn, parseNonParameterInitializer);
|
|
|
|
parseSemicolon();
|
|
return finishNode(property);
|
|
}
|
|
|
|
function parsePropertyOrMethodDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ClassElement {
|
|
let asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
|
|
let name = parsePropertyName();
|
|
|
|
// Note: this is not legal as per the grammar. But we allow it in the parser and
|
|
// report an error in the grammar checker.
|
|
let questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
|
|
if (asteriskToken || token === SyntaxKind.OpenParenToken || token === SyntaxKind.LessThanToken) {
|
|
return parseMethodDeclaration(fullStart, decorators, modifiers, asteriskToken, name, questionToken, Diagnostics.or_expected);
|
|
}
|
|
else {
|
|
return parsePropertyDeclaration(fullStart, decorators, modifiers, name, questionToken);
|
|
}
|
|
}
|
|
|
|
function parseNonParameterInitializer() {
|
|
return parseInitializer(/*inParameter*/ false);
|
|
}
|
|
|
|
function parseAccessorDeclaration(kind: SyntaxKind, fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): AccessorDeclaration {
|
|
let node = <AccessorDeclaration>createNode(kind, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
node.name = parsePropertyName();
|
|
fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ false, /*requireCompleteParameterList*/ false, node);
|
|
node.body = parseFunctionBlockOrSemicolon(/*isGenerator*/ false);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function isClassMemberModifier(idToken: SyntaxKind) {
|
|
switch (idToken) {
|
|
case SyntaxKind.PublicKeyword:
|
|
case SyntaxKind.PrivateKeyword:
|
|
case SyntaxKind.ProtectedKeyword:
|
|
case SyntaxKind.StaticKeyword:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isClassMemberStart(): boolean {
|
|
let idToken: SyntaxKind;
|
|
|
|
if (token === SyntaxKind.AtToken) {
|
|
return true;
|
|
}
|
|
|
|
// Eat up all modifiers, but hold on to the last one in case it is actually an identifier.
|
|
while (isModifier(token)) {
|
|
idToken = token;
|
|
// If the idToken is a class modifier (protected, private, public, and static), it is
|
|
// certain that we are starting to parse class member. This allows better error recovery
|
|
// Example:
|
|
// public foo() ... // true
|
|
// public @dec blah ... // true; we will then report an error later
|
|
// export public ... // true; we will then report an error later
|
|
if (isClassMemberModifier(idToken)) {
|
|
return true;
|
|
}
|
|
|
|
nextToken();
|
|
}
|
|
|
|
if (token === SyntaxKind.AsteriskToken) {
|
|
return true;
|
|
}
|
|
|
|
// Try to get the first property-like token following all modifiers.
|
|
// This can either be an identifier or the 'get' or 'set' keywords.
|
|
if (isLiteralPropertyName()) {
|
|
idToken = token;
|
|
nextToken();
|
|
}
|
|
|
|
// Index signatures and computed properties are class members; we can parse.
|
|
if (token === SyntaxKind.OpenBracketToken) {
|
|
return true;
|
|
}
|
|
|
|
// If we were able to get any potential identifier...
|
|
if (idToken !== undefined) {
|
|
// If we have a non-keyword identifier, or if we have an accessor, then it's safe to parse.
|
|
if (!isKeyword(idToken) || idToken === SyntaxKind.SetKeyword || idToken === SyntaxKind.GetKeyword) {
|
|
return true;
|
|
}
|
|
|
|
// If it *is* a keyword, but not an accessor, check a little farther along
|
|
// to see if it should actually be parsed as a class member.
|
|
switch (token) {
|
|
case SyntaxKind.OpenParenToken: // Method declaration
|
|
case SyntaxKind.LessThanToken: // Generic Method declaration
|
|
case SyntaxKind.ColonToken: // Type Annotation for declaration
|
|
case SyntaxKind.EqualsToken: // Initializer for declaration
|
|
case SyntaxKind.QuestionToken: // Not valid, but permitted so that it gets caught later on.
|
|
return true;
|
|
default:
|
|
// Covers
|
|
// - Semicolons (declaration termination)
|
|
// - Closing braces (end-of-class, must be declaration)
|
|
// - End-of-files (not valid, but permitted so that it gets caught later on)
|
|
// - Line-breaks (enabling *automatic semicolon insertion*)
|
|
return canParseSemicolon();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function parseDecorators(): NodeArray<Decorator> {
|
|
let decorators: NodeArray<Decorator>;
|
|
while (true) {
|
|
let decoratorStart = getNodePos();
|
|
if (!parseOptional(SyntaxKind.AtToken)) {
|
|
break;
|
|
}
|
|
|
|
if (!decorators) {
|
|
decorators = <NodeArray<Decorator>>[];
|
|
decorators.pos = scanner.getStartPos();
|
|
}
|
|
|
|
let decorator = <Decorator>createNode(SyntaxKind.Decorator, decoratorStart);
|
|
decorator.expression = doInDecoratorContext(parseLeftHandSideExpressionOrHigher);
|
|
decorators.push(finishNode(decorator));
|
|
}
|
|
if (decorators) {
|
|
decorators.end = getNodeEnd();
|
|
}
|
|
return decorators;
|
|
}
|
|
|
|
function parseModifiers(): ModifiersArray {
|
|
let flags = 0;
|
|
let modifiers: ModifiersArray;
|
|
while (true) {
|
|
let modifierStart = scanner.getStartPos();
|
|
let modifierKind = token;
|
|
|
|
if (!parseAnyContextualModifier()) {
|
|
break;
|
|
}
|
|
|
|
if (!modifiers) {
|
|
modifiers = <ModifiersArray>[];
|
|
modifiers.pos = modifierStart;
|
|
}
|
|
flags |= modifierToFlag(modifierKind);
|
|
modifiers.push(finishNode(createNode(modifierKind, modifierStart)));
|
|
}
|
|
if (modifiers) {
|
|
modifiers.flags = flags;
|
|
modifiers.end = scanner.getStartPos();
|
|
}
|
|
return modifiers;
|
|
}
|
|
|
|
function parseClassElement(): ClassElement {
|
|
if (token === SyntaxKind.SemicolonToken) {
|
|
let result = <SemicolonClassElement>createNode(SyntaxKind.SemicolonClassElement);
|
|
nextToken();
|
|
return finishNode(result);
|
|
}
|
|
|
|
let fullStart = getNodePos();
|
|
let decorators = parseDecorators();
|
|
let modifiers = parseModifiers();
|
|
|
|
let accessor = tryParseAccessorDeclaration(fullStart, decorators, modifiers);
|
|
if (accessor) {
|
|
return accessor;
|
|
}
|
|
|
|
if (token === SyntaxKind.ConstructorKeyword) {
|
|
return parseConstructorDeclaration(fullStart, decorators, modifiers);
|
|
}
|
|
|
|
if (isIndexSignature()) {
|
|
return parseIndexSignatureDeclaration(fullStart, decorators, modifiers);
|
|
}
|
|
|
|
// It is very important that we check this *after* checking indexers because
|
|
// the [ token can start an index signature or a computed property name
|
|
if (isIdentifierOrKeyword() ||
|
|
token === SyntaxKind.StringLiteral ||
|
|
token === SyntaxKind.NumericLiteral ||
|
|
token === SyntaxKind.AsteriskToken ||
|
|
token === SyntaxKind.OpenBracketToken) {
|
|
|
|
return parsePropertyOrMethodDeclaration(fullStart, decorators, modifiers);
|
|
}
|
|
|
|
if (decorators) {
|
|
// treat this as a property declaration with a missing name.
|
|
let name = <Identifier>createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected);
|
|
return parsePropertyDeclaration(fullStart, decorators, modifiers, name, /*questionToken*/ undefined);
|
|
}
|
|
|
|
// 'isClassMemberStart' should have hinted not to attempt parsing.
|
|
Debug.fail("Should not have attempted to parse class member declaration.");
|
|
}
|
|
|
|
function parseClassExpression(): ClassExpression {
|
|
return <ClassExpression>parseClassDeclarationOrExpression(
|
|
/*fullStart*/ scanner.getStartPos(),
|
|
/*decorators*/ undefined,
|
|
/*modifiers*/ undefined,
|
|
SyntaxKind.ClassExpression);
|
|
}
|
|
|
|
function parseClassDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ClassDeclaration {
|
|
return <ClassDeclaration>parseClassDeclarationOrExpression(fullStart, decorators, modifiers, SyntaxKind.ClassDeclaration);
|
|
}
|
|
|
|
function parseClassDeclarationOrExpression(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray, kind: SyntaxKind): ClassLikeDeclaration {
|
|
// In ES6 specification, All parts of a ClassDeclaration or a ClassExpression are strict mode code
|
|
let savedStrictModeContext = inStrictModeContext();
|
|
setStrictModeContext(true);
|
|
|
|
var node = <ClassLikeDeclaration>createNode(kind, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
parseExpected(SyntaxKind.ClassKeyword);
|
|
node.name = parseOptionalIdentifier();
|
|
node.typeParameters = parseTypeParameters();
|
|
node.heritageClauses = parseHeritageClauses(/*isClassHeritageClause*/ true);
|
|
|
|
if (parseExpected(SyntaxKind.OpenBraceToken)) {
|
|
// ClassTail[Yield,GeneratorParameter] : See 14.5
|
|
// [~GeneratorParameter]ClassHeritage[?Yield]opt { ClassBody[?Yield]opt }
|
|
// [+GeneratorParameter] ClassHeritageopt { ClassBodyopt }
|
|
|
|
node.members = inGeneratorParameterContext()
|
|
? doOutsideOfYieldContext(parseClassMembers)
|
|
: parseClassMembers();
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
}
|
|
else {
|
|
node.members = createMissingList<ClassElement>();
|
|
}
|
|
|
|
var finishedNode = finishNode(node);
|
|
setStrictModeContext(savedStrictModeContext);
|
|
return finishedNode;
|
|
}
|
|
|
|
function parseHeritageClauses(isClassHeritageClause: boolean): NodeArray<HeritageClause> {
|
|
// ClassTail[Yield,GeneratorParameter] : See 14.5
|
|
// [~GeneratorParameter]ClassHeritage[?Yield]opt { ClassBody[?Yield]opt }
|
|
// [+GeneratorParameter] ClassHeritageopt { ClassBodyopt }
|
|
|
|
if (isHeritageClause()) {
|
|
return isClassHeritageClause && inGeneratorParameterContext()
|
|
? doOutsideOfYieldContext(parseHeritageClausesWorker)
|
|
: parseHeritageClausesWorker();
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function parseHeritageClausesWorker() {
|
|
return parseList(ParsingContext.HeritageClauses, /*checkForStrictMode*/ false, parseHeritageClause);
|
|
}
|
|
|
|
function parseHeritageClause() {
|
|
if (token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword) {
|
|
let node = <HeritageClause>createNode(SyntaxKind.HeritageClause);
|
|
node.token = token;
|
|
nextToken();
|
|
node.types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments);
|
|
return finishNode(node);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments {
|
|
let node = <ExpressionWithTypeArguments>createNode(SyntaxKind.ExpressionWithTypeArguments);
|
|
node.expression = parseLeftHandSideExpressionOrHigher();
|
|
if (token === SyntaxKind.LessThanToken) {
|
|
node.typeArguments = parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken);
|
|
}
|
|
|
|
return finishNode(node);
|
|
}
|
|
|
|
function isHeritageClause(): boolean {
|
|
return token === SyntaxKind.ExtendsKeyword || token === SyntaxKind.ImplementsKeyword;
|
|
}
|
|
|
|
function parseClassMembers() {
|
|
return parseList(ParsingContext.ClassMembers, /*checkForStrictMode*/ false, parseClassElement);
|
|
}
|
|
|
|
function parseInterfaceDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): InterfaceDeclaration {
|
|
let node = <InterfaceDeclaration>createNode(SyntaxKind.InterfaceDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
parseExpected(SyntaxKind.InterfaceKeyword);
|
|
node.name = parseIdentifier();
|
|
node.typeParameters = parseTypeParameters();
|
|
node.heritageClauses = parseHeritageClauses(/*isClassHeritageClause*/ false);
|
|
node.members = parseObjectTypeMembers();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseTypeAliasDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): TypeAliasDeclaration {
|
|
let node = <TypeAliasDeclaration>createNode(SyntaxKind.TypeAliasDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
parseExpected(SyntaxKind.TypeKeyword);
|
|
node.name = parseIdentifier();
|
|
parseExpected(SyntaxKind.EqualsToken);
|
|
node.type = parseType();
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
// In an ambient declaration, the grammar only allows integer literals as initializers.
|
|
// In a non-ambient declaration, the grammar allows uninitialized members only in a
|
|
// ConstantEnumMemberSection, which starts at the beginning of an enum declaration
|
|
// or any time an integer literal initializer is encountered.
|
|
function parseEnumMember(): EnumMember {
|
|
let node = <EnumMember>createNode(SyntaxKind.EnumMember, scanner.getStartPos());
|
|
node.name = parsePropertyName();
|
|
node.initializer = allowInAnd(parseNonParameterInitializer);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseEnumDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): EnumDeclaration {
|
|
let node = <EnumDeclaration>createNode(SyntaxKind.EnumDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
parseExpected(SyntaxKind.EnumKeyword);
|
|
node.name = parseIdentifier();
|
|
if (parseExpected(SyntaxKind.OpenBraceToken)) {
|
|
node.members = parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
}
|
|
else {
|
|
node.members = createMissingList<EnumMember>();
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseModuleBlock(): ModuleBlock {
|
|
let node = <ModuleBlock>createNode(SyntaxKind.ModuleBlock, scanner.getStartPos());
|
|
if (parseExpected(SyntaxKind.OpenBraceToken)) {
|
|
node.statements = parseList(ParsingContext.ModuleElements, /*checkForStrictMode*/false, parseModuleElement);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
}
|
|
else {
|
|
node.statements = createMissingList<Statement>();
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseModuleOrNamespaceDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray, flags: NodeFlags): ModuleDeclaration {
|
|
let node = <ModuleDeclaration>createNode(SyntaxKind.ModuleDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
node.flags |= flags;
|
|
node.name = parseIdentifier();
|
|
node.body = parseOptional(SyntaxKind.DotToken)
|
|
? parseModuleOrNamespaceDeclaration(getNodePos(), /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.Export)
|
|
: parseModuleBlock();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseAmbientExternalModuleDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ModuleDeclaration {
|
|
let node = <ModuleDeclaration>createNode(SyntaxKind.ModuleDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
node.name = parseLiteralNode(/*internName*/ true);
|
|
node.body = parseModuleBlock();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseModuleDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ModuleDeclaration {
|
|
let flags = modifiers ? modifiers.flags : 0;
|
|
if (parseOptional(SyntaxKind.NamespaceKeyword)) {
|
|
flags |= NodeFlags.Namespace;
|
|
}
|
|
else {
|
|
parseExpected(SyntaxKind.ModuleKeyword);
|
|
if (token === SyntaxKind.StringLiteral) {
|
|
return parseAmbientExternalModuleDeclaration(fullStart, decorators, modifiers);
|
|
}
|
|
}
|
|
return parseModuleOrNamespaceDeclaration(fullStart, decorators, modifiers, flags);
|
|
}
|
|
|
|
function isExternalModuleReference() {
|
|
return token === SyntaxKind.RequireKeyword &&
|
|
lookAhead(nextTokenIsOpenParen);
|
|
}
|
|
|
|
function nextTokenIsOpenParen() {
|
|
return nextToken() === SyntaxKind.OpenParenToken;
|
|
}
|
|
|
|
function nextTokenIsCommaOrFromKeyword() {
|
|
nextToken();
|
|
return token === SyntaxKind.CommaToken ||
|
|
token === SyntaxKind.FromKeyword;
|
|
}
|
|
|
|
function parseImportDeclarationOrImportEqualsDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ImportEqualsDeclaration | ImportDeclaration {
|
|
parseExpected(SyntaxKind.ImportKeyword);
|
|
let afterImportPos = scanner.getStartPos();
|
|
|
|
let identifier: Identifier;
|
|
if (isIdentifier()) {
|
|
identifier = parseIdentifier();
|
|
if (token !== SyntaxKind.CommaToken && token !== SyntaxKind.FromKeyword) {
|
|
// ImportEquals declaration of type:
|
|
// import x = require("mod"); or
|
|
// import x = M.x;
|
|
let importEqualsDeclaration = <ImportEqualsDeclaration>createNode(SyntaxKind.ImportEqualsDeclaration, fullStart);
|
|
importEqualsDeclaration.decorators = decorators;
|
|
setModifiers(importEqualsDeclaration, modifiers);
|
|
importEqualsDeclaration.name = identifier;
|
|
parseExpected(SyntaxKind.EqualsToken);
|
|
importEqualsDeclaration.moduleReference = parseModuleReference();
|
|
parseSemicolon();
|
|
return finishNode(importEqualsDeclaration);
|
|
}
|
|
}
|
|
|
|
// Import statement
|
|
let importDeclaration = <ImportDeclaration>createNode(SyntaxKind.ImportDeclaration, fullStart);
|
|
importDeclaration.decorators = decorators;
|
|
setModifiers(importDeclaration, modifiers);
|
|
|
|
// ImportDeclaration:
|
|
// import ImportClause from ModuleSpecifier ;
|
|
// import ModuleSpecifier;
|
|
if (identifier || // import id
|
|
token === SyntaxKind.AsteriskToken || // import *
|
|
token === SyntaxKind.OpenBraceToken) { // import {
|
|
importDeclaration.importClause = parseImportClause(identifier, afterImportPos);
|
|
parseExpected(SyntaxKind.FromKeyword);
|
|
}
|
|
|
|
importDeclaration.moduleSpecifier = parseModuleSpecifier();
|
|
parseSemicolon();
|
|
return finishNode(importDeclaration);
|
|
}
|
|
|
|
function parseImportClause(identifier: Identifier, fullStart: number) {
|
|
//ImportClause:
|
|
// ImportedDefaultBinding
|
|
// NameSpaceImport
|
|
// NamedImports
|
|
// ImportedDefaultBinding, NameSpaceImport
|
|
// ImportedDefaultBinding, NamedImports
|
|
|
|
let importClause = <ImportClause>createNode(SyntaxKind.ImportClause, fullStart);
|
|
if (identifier) {
|
|
// ImportedDefaultBinding:
|
|
// ImportedBinding
|
|
importClause.name = identifier;
|
|
}
|
|
|
|
// If there was no default import or if there is comma token after default import
|
|
// parse namespace or named imports
|
|
if (!importClause.name ||
|
|
parseOptional(SyntaxKind.CommaToken)) {
|
|
importClause.namedBindings = token === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports);
|
|
}
|
|
|
|
return finishNode(importClause);
|
|
}
|
|
|
|
function parseModuleReference() {
|
|
return isExternalModuleReference()
|
|
? parseExternalModuleReference()
|
|
: parseEntityName(/*allowReservedWords*/ false);
|
|
}
|
|
|
|
function parseExternalModuleReference() {
|
|
let node = <ExternalModuleReference>createNode(SyntaxKind.ExternalModuleReference);
|
|
parseExpected(SyntaxKind.RequireKeyword);
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
node.expression = parseModuleSpecifier();
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseModuleSpecifier(): Expression {
|
|
// We allow arbitrary expressions here, even though the grammar only allows string
|
|
// literals. We check to ensure that it is only a string literal later in the grammar
|
|
// walker.
|
|
let result = parseExpression();
|
|
// Ensure the string being required is in our 'identifier' table. This will ensure
|
|
// that features like 'find refs' will look inside this file when search for its name.
|
|
if (result.kind === SyntaxKind.StringLiteral) {
|
|
internIdentifier((<LiteralExpression>result).text);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function parseNamespaceImport(): NamespaceImport {
|
|
// NameSpaceImport:
|
|
// * as ImportedBinding
|
|
let namespaceImport = <NamespaceImport>createNode(SyntaxKind.NamespaceImport);
|
|
parseExpected(SyntaxKind.AsteriskToken);
|
|
parseExpected(SyntaxKind.AsKeyword);
|
|
namespaceImport.name = parseIdentifier();
|
|
return finishNode(namespaceImport);
|
|
}
|
|
|
|
function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports {
|
|
let node = <NamedImports>createNode(kind);
|
|
|
|
// NamedImports:
|
|
// { }
|
|
// { ImportsList }
|
|
// { ImportsList, }
|
|
|
|
// ImportsList:
|
|
// ImportSpecifier
|
|
// ImportsList, ImportSpecifier
|
|
node.elements = parseBracketedList(ParsingContext.ImportOrExportSpecifiers,
|
|
kind === SyntaxKind.NamedImports ? parseImportSpecifier : parseExportSpecifier,
|
|
SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken);
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseExportSpecifier() {
|
|
return parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier);
|
|
}
|
|
|
|
function parseImportSpecifier() {
|
|
return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier);
|
|
}
|
|
|
|
function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier {
|
|
let node = <ImportSpecifier>createNode(kind);
|
|
// ImportSpecifier:
|
|
// BindingIdentifier
|
|
// IdentifierName as BindingIdentifier
|
|
// ExportSpecififer:
|
|
// IdentifierName
|
|
// IdentifierName as IdentifierName
|
|
let checkIdentifierIsKeyword = isKeyword(token) && !isIdentifier();
|
|
let checkIdentifierStart = scanner.getTokenPos();
|
|
let checkIdentifierEnd = scanner.getTextPos();
|
|
let identifierName = parseIdentifierName();
|
|
if (token === SyntaxKind.AsKeyword) {
|
|
node.propertyName = identifierName;
|
|
parseExpected(SyntaxKind.AsKeyword);
|
|
checkIdentifierIsKeyword = isKeyword(token) && !isIdentifier();
|
|
checkIdentifierStart = scanner.getTokenPos();
|
|
checkIdentifierEnd = scanner.getTextPos();
|
|
node.name = parseIdentifierName();
|
|
}
|
|
else {
|
|
node.name = identifierName;
|
|
}
|
|
if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) {
|
|
// Report error identifier expected
|
|
parseErrorAtPosition(checkIdentifierStart, checkIdentifierEnd - checkIdentifierStart, Diagnostics.Identifier_expected);
|
|
}
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseExportDeclaration(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ExportDeclaration {
|
|
let node = <ExportDeclaration>createNode(SyntaxKind.ExportDeclaration, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
if (parseOptional(SyntaxKind.AsteriskToken)) {
|
|
parseExpected(SyntaxKind.FromKeyword);
|
|
node.moduleSpecifier = parseModuleSpecifier();
|
|
}
|
|
else {
|
|
node.exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports);
|
|
if (parseOptional(SyntaxKind.FromKeyword)) {
|
|
node.moduleSpecifier = parseModuleSpecifier();
|
|
}
|
|
}
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function parseExportAssignment(fullStart: number, decorators: NodeArray<Decorator>, modifiers: ModifiersArray): ExportAssignment {
|
|
let node = <ExportAssignment>createNode(SyntaxKind.ExportAssignment, fullStart);
|
|
node.decorators = decorators;
|
|
setModifiers(node, modifiers);
|
|
if (parseOptional(SyntaxKind.EqualsToken)) {
|
|
node.isExportEquals = true;
|
|
}
|
|
else {
|
|
parseExpected(SyntaxKind.DefaultKeyword);
|
|
}
|
|
node.expression = parseAssignmentExpressionOrHigher();
|
|
parseSemicolon();
|
|
return finishNode(node);
|
|
}
|
|
|
|
function processReferenceComments(sourceFile: SourceFile): void {
|
|
let triviaScanner = createScanner(sourceFile.languageVersion, /*skipTrivia*/false, sourceText);
|
|
let referencedFiles: FileReference[] = [];
|
|
let amdDependencies: { path: string; name: string }[] = [];
|
|
let amdModuleName: string;
|
|
|
|
// Keep scanning all the leading trivia in the file until we get to something that
|
|
// isn't trivia. Any single line comment will be analyzed to see if it is a
|
|
// reference comment.
|
|
while (true) {
|
|
let kind = triviaScanner.scan();
|
|
if (kind === SyntaxKind.WhitespaceTrivia || kind === SyntaxKind.NewLineTrivia || kind === SyntaxKind.MultiLineCommentTrivia) {
|
|
continue;
|
|
}
|
|
if (kind !== SyntaxKind.SingleLineCommentTrivia) {
|
|
break;
|
|
}
|
|
|
|
let range = { pos: triviaScanner.getTokenPos(), end: triviaScanner.getTextPos(), kind: triviaScanner.getToken() };
|
|
|
|
let comment = sourceText.substring(range.pos, range.end);
|
|
let referencePathMatchResult = getFileReferenceFromReferencePath(comment, range);
|
|
if (referencePathMatchResult) {
|
|
let fileReference = referencePathMatchResult.fileReference;
|
|
sourceFile.hasNoDefaultLib = referencePathMatchResult.isNoDefaultLib;
|
|
let diagnosticMessage = referencePathMatchResult.diagnosticMessage;
|
|
if (fileReference) {
|
|
referencedFiles.push(fileReference);
|
|
}
|
|
if (diagnosticMessage) {
|
|
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, diagnosticMessage));
|
|
}
|
|
}
|
|
else {
|
|
let amdModuleNameRegEx = /^\/\/\/\s*<amd-module\s+name\s*=\s*('|")(.+?)\1/gim;
|
|
let amdModuleNameMatchResult = amdModuleNameRegEx.exec(comment);
|
|
if (amdModuleNameMatchResult) {
|
|
if (amdModuleName) {
|
|
parseDiagnostics.push(createFileDiagnostic(sourceFile, range.pos, range.end - range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments));
|
|
}
|
|
amdModuleName = amdModuleNameMatchResult[2];
|
|
}
|
|
|
|
let amdDependencyRegEx = /^\/\/\/\s*<amd-dependency\s/gim;
|
|
let pathRegex = /\spath\s*=\s*('|")(.+?)\1/gim;
|
|
let nameRegex = /\sname\s*=\s*('|")(.+?)\1/gim;
|
|
let amdDependencyMatchResult = amdDependencyRegEx.exec(comment);
|
|
if (amdDependencyMatchResult) {
|
|
let pathMatchResult = pathRegex.exec(comment);
|
|
let nameMatchResult = nameRegex.exec(comment);
|
|
if (pathMatchResult) {
|
|
let amdDependency = { path: pathMatchResult[2], name: nameMatchResult ? nameMatchResult[2] : undefined };
|
|
amdDependencies.push(amdDependency);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sourceFile.referencedFiles = referencedFiles;
|
|
sourceFile.amdDependencies = amdDependencies;
|
|
sourceFile.amdModuleName = amdModuleName;
|
|
}
|
|
|
|
function setExternalModuleIndicator(sourceFile: SourceFile) {
|
|
sourceFile.externalModuleIndicator = forEach(sourceFile.statements, node =>
|
|
node.flags & NodeFlags.Export
|
|
|| node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind === SyntaxKind.ExternalModuleReference
|
|
|| node.kind === SyntaxKind.ImportDeclaration
|
|
|| node.kind === SyntaxKind.ExportAssignment
|
|
|| node.kind === SyntaxKind.ExportDeclaration
|
|
? node
|
|
: undefined);
|
|
}
|
|
|
|
const enum ParsingContext {
|
|
SourceElements, // Elements in source file
|
|
ModuleElements, // Elements in module declaration
|
|
BlockStatements, // Statements in block
|
|
SwitchClauses, // Clauses in switch statement
|
|
SwitchClauseStatements, // Statements in switch clause
|
|
TypeMembers, // Members in interface or type literal
|
|
ClassMembers, // Members in class declaration
|
|
EnumMembers, // Members in enum declaration
|
|
HeritageClauseElement, // Elements in a heritage clause
|
|
VariableDeclarations, // Variable declarations in variable statement
|
|
ObjectBindingElements, // Binding elements in object binding list
|
|
ArrayBindingElements, // Binding elements in array binding list
|
|
ArgumentExpressions, // Expressions in argument list
|
|
ObjectLiteralMembers, // Members in object literal
|
|
ArrayLiteralMembers, // Members in array literal
|
|
Parameters, // Parameters in parameter list
|
|
TypeParameters, // Type parameters in type parameter list
|
|
TypeArguments, // Type arguments in type argument list
|
|
TupleElementTypes, // Element types in tuple element type list
|
|
HeritageClauses, // Heritage clauses for a class or interface declaration.
|
|
ImportOrExportSpecifiers, // Named import clause's import specifier list
|
|
JSDocFunctionParameters,
|
|
JSDocTypeArguments,
|
|
JSDocRecordMembers,
|
|
JSDocTupleTypes,
|
|
Count // Number of parsing contexts
|
|
}
|
|
|
|
const enum Tristate {
|
|
False,
|
|
True,
|
|
Unknown
|
|
}
|
|
|
|
export module JSDocParser {
|
|
export function isJSDocType() {
|
|
switch (token) {
|
|
case SyntaxKind.AsteriskToken:
|
|
case SyntaxKind.QuestionToken:
|
|
case SyntaxKind.OpenParenToken:
|
|
case SyntaxKind.OpenBracketToken:
|
|
case SyntaxKind.ExclamationToken:
|
|
case SyntaxKind.OpenBraceToken:
|
|
case SyntaxKind.FunctionKeyword:
|
|
case SyntaxKind.DotDotDotToken:
|
|
case SyntaxKind.NewKeyword:
|
|
case SyntaxKind.ThisKeyword:
|
|
return true;
|
|
}
|
|
|
|
return isIdentifierOrKeyword();
|
|
}
|
|
|
|
export function parseJSDocTypeExpressionForTests(content: string, start: number, length: number) {
|
|
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined);
|
|
let jsDocTypeExpression = parseJSDocTypeExpression(start, length);
|
|
let diagnostics = parseDiagnostics;
|
|
clearState();
|
|
|
|
return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined;
|
|
}
|
|
|
|
// Parses out a JSDoc type expression. The starting position should be right at the open
|
|
// curly in the type expression. Returns 'undefined' if it encounters any errors while parsing.
|
|
/* @internal */
|
|
export function parseJSDocTypeExpression(start: number, length: number): JSDocTypeExpression {
|
|
scanner.setText(sourceText, start, length);
|
|
|
|
// Prime the first token for us to start processing.
|
|
token = nextToken();
|
|
|
|
let result = <JSDocTypeExpression>createNode(SyntaxKind.JSDocTypeExpression);
|
|
|
|
parseExpected(SyntaxKind.OpenBraceToken);
|
|
result.type = parseJSDocTopLevelType();
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
|
|
fixupParentReferences(result);
|
|
return finishNode(result);
|
|
}
|
|
|
|
function setError(message: DiagnosticMessage) {
|
|
parseErrorAtCurrentToken(message);
|
|
if (throwOnJSDocErrors) {
|
|
throw new Error(message.key);
|
|
}
|
|
}
|
|
|
|
function parseJSDocTopLevelType(): JSDocType {
|
|
var type = parseJSDocType();
|
|
if (token === SyntaxKind.BarToken) {
|
|
var unionType = <JSDocUnionType>createNode(SyntaxKind.JSDocUnionType, type.pos);
|
|
unionType.types = parseJSDocTypeList(type);
|
|
type = finishNode(unionType);
|
|
}
|
|
|
|
if (token === SyntaxKind.EqualsToken) {
|
|
var optionalType = <JSDocOptionalType>createNode(SyntaxKind.JSDocOptionalType, type.pos);
|
|
nextToken();
|
|
optionalType.type = type;
|
|
type = finishNode(optionalType);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
function parseJSDocType(): JSDocType {
|
|
let type = parseBasicTypeExpression();
|
|
|
|
while (true) {
|
|
if (token === SyntaxKind.OpenBracketToken) {
|
|
let arrayType = <JSDocArrayType>createNode(SyntaxKind.JSDocArrayType, type.pos);
|
|
arrayType.elementType = type;
|
|
|
|
nextToken();
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
|
|
type = finishNode(arrayType);
|
|
}
|
|
else if (token === SyntaxKind.QuestionToken) {
|
|
let nullableType = <JSDocNullableType>createNode(SyntaxKind.JSDocNullableType, type.pos);
|
|
nullableType.type = type;
|
|
|
|
nextToken();
|
|
type = finishNode(nullableType);
|
|
}
|
|
else if (token === SyntaxKind.ExclamationToken) {
|
|
let nonNullableType = <JSDocNonNullableType>createNode(SyntaxKind.JSDocNonNullableType, type.pos);
|
|
nonNullableType.type = type;
|
|
|
|
nextToken();
|
|
type = finishNode(nonNullableType);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
function parseBasicTypeExpression(): JSDocType {
|
|
switch (token) {
|
|
case SyntaxKind.AsteriskToken:
|
|
return parseJSDocAllType();
|
|
case SyntaxKind.QuestionToken:
|
|
return parseJSDocUnknownOrNullableType();
|
|
case SyntaxKind.OpenParenToken:
|
|
return parseJSDocUnionType();
|
|
case SyntaxKind.OpenBracketToken:
|
|
return parseJSDocTupleType();
|
|
case SyntaxKind.ExclamationToken:
|
|
return parseJSDocNonNullableType();
|
|
case SyntaxKind.OpenBraceToken:
|
|
return parseJSDocRecordType();
|
|
case SyntaxKind.FunctionKeyword:
|
|
return parseJSDocFunctionType();
|
|
case SyntaxKind.DotDotDotToken:
|
|
return parseJSDocVariadicType();
|
|
case SyntaxKind.NewKeyword:
|
|
return parseJSDocConstructorType();
|
|
case SyntaxKind.ThisKeyword:
|
|
return parseJSDocThisType();
|
|
case SyntaxKind.AnyKeyword:
|
|
case SyntaxKind.StringKeyword:
|
|
case SyntaxKind.NumberKeyword:
|
|
case SyntaxKind.BooleanKeyword:
|
|
case SyntaxKind.SymbolKeyword:
|
|
case SyntaxKind.VoidKeyword:
|
|
return parseTokenNode<JSDocType>();
|
|
}
|
|
|
|
return parseJSDocTypeReference();
|
|
}
|
|
|
|
function parseJSDocThisType(): JSDocThisType {
|
|
let result = <JSDocThisType>createNode(SyntaxKind.JSDocThisType);
|
|
nextToken();
|
|
parseExpected(SyntaxKind.ColonToken);
|
|
result.type = parseJSDocType();
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocConstructorType(): JSDocConstructorType {
|
|
let result = <JSDocConstructorType>createNode(SyntaxKind.JSDocConstructorType);
|
|
nextToken();
|
|
parseExpected(SyntaxKind.ColonToken);
|
|
result.type = parseJSDocType();
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocVariadicType(): JSDocVariadicType {
|
|
let result = <JSDocVariadicType>createNode(SyntaxKind.JSDocVariadicType);
|
|
nextToken();
|
|
result.type = parseJSDocType();
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocFunctionType(): JSDocFunctionType {
|
|
let result = <JSDocFunctionType>createNode(SyntaxKind.JSDocFunctionType);
|
|
nextToken();
|
|
|
|
parseExpected(SyntaxKind.OpenParenToken);
|
|
result.parameters = parseDelimitedList(ParsingContext.JSDocFunctionParameters, parseJSDocParameter);
|
|
checkForTrailingComma(result.parameters);
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
|
|
if (token === SyntaxKind.ColonToken) {
|
|
nextToken();
|
|
result.type = parseJSDocType();
|
|
}
|
|
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocParameter(): ParameterDeclaration {
|
|
let parameter = <ParameterDeclaration>createNode(SyntaxKind.Parameter);
|
|
parameter.type = parseJSDocType();
|
|
return finishNode(parameter);
|
|
}
|
|
|
|
function parseJSDocOptionalType(type: JSDocType): JSDocOptionalType {
|
|
let result = <JSDocOptionalType>createNode(SyntaxKind.JSDocOptionalType, type.pos);
|
|
nextToken();
|
|
result.type = type;
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocTypeReference(): JSDocTypeReference {
|
|
let result = <JSDocTypeReference>createNode(SyntaxKind.JSDocTypeReference);
|
|
result.name = parseSimplePropertyName();
|
|
|
|
while (parseOptional(SyntaxKind.DotToken)) {
|
|
if (token === SyntaxKind.LessThanToken) {
|
|
result.typeArguments = parseTypeArguments();
|
|
break;
|
|
}
|
|
else {
|
|
result.name = parseQualifiedName(result.name);
|
|
}
|
|
}
|
|
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseTypeArguments() {
|
|
// Move past the <
|
|
nextToken();
|
|
let typeArguments = parseDelimitedList(ParsingContext.JSDocTypeArguments, parseJSDocType);
|
|
checkForTrailingComma(typeArguments);
|
|
checkForEmptyTypeArgumentList(typeArguments);
|
|
parseExpected(SyntaxKind.GreaterThanToken);
|
|
|
|
return typeArguments;
|
|
}
|
|
|
|
function checkForEmptyTypeArgumentList(typeArguments: NodeArray<Node>) {
|
|
if (parseDiagnostics.length === 0 && typeArguments && typeArguments.length === 0) {
|
|
let start = typeArguments.pos - "<".length;
|
|
let end = skipTrivia(sourceText, typeArguments.end) + ">".length;
|
|
return parseErrorAtPosition(start, end - start, Diagnostics.Type_argument_list_cannot_be_empty);
|
|
}
|
|
}
|
|
|
|
function parseQualifiedName(left: EntityName): QualifiedName {
|
|
let result = <QualifiedName>createNode(SyntaxKind.QualifiedName, left.pos);
|
|
result.left = left;
|
|
result.right = parseIdentifierName();
|
|
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocRecordType(): JSDocRecordType {
|
|
let result = <JSDocRecordType>createNode(SyntaxKind.JSDocRecordType);
|
|
nextToken();
|
|
result.members = parseDelimitedList(ParsingContext.JSDocRecordMembers, parseJSDocRecordMember);
|
|
checkForTrailingComma(result.members);
|
|
parseExpected(SyntaxKind.CloseBraceToken);
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocRecordMember(): JSDocRecordMember {
|
|
let result = <JSDocRecordMember>createNode(SyntaxKind.JSDocRecordMember);
|
|
result.name = parseSimplePropertyName();
|
|
|
|
if (token === SyntaxKind.ColonToken) {
|
|
nextToken();
|
|
result.type = parseJSDocType();
|
|
}
|
|
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocNonNullableType(): JSDocNonNullableType {
|
|
let result = <JSDocNonNullableType>createNode(SyntaxKind.JSDocNonNullableType);
|
|
nextToken();
|
|
result.type = parseJSDocType();
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocTupleType(): JSDocTupleType {
|
|
let result = <JSDocTupleType>createNode(SyntaxKind.JSDocTupleType);
|
|
nextToken();
|
|
result.types = parseDelimitedList(ParsingContext.JSDocTupleTypes, parseJSDocType);
|
|
checkForTrailingComma(result.types);
|
|
parseExpected(SyntaxKind.CloseBracketToken);
|
|
|
|
return finishNode(result);
|
|
}
|
|
|
|
function checkForTrailingComma(list: NodeArray<Node>) {
|
|
if (parseDiagnostics.length === 0 && list.hasTrailingComma) {
|
|
let start = list.end - ",".length;
|
|
parseErrorAtPosition(start, ",".length, Diagnostics.Trailing_comma_not_allowed);
|
|
}
|
|
}
|
|
|
|
function parseJSDocUnionType(): JSDocUnionType {
|
|
let result = <JSDocUnionType>createNode(SyntaxKind.JSDocUnionType);
|
|
nextToken();
|
|
result.types = parseJSDocTypeList(parseJSDocType());
|
|
|
|
parseExpected(SyntaxKind.CloseParenToken);
|
|
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocTypeList(firstType: JSDocType) {
|
|
Debug.assert(!!firstType);
|
|
|
|
let types = <NodeArray<JSDocType>>[];
|
|
types.pos = firstType.pos;
|
|
|
|
types.push(firstType);
|
|
while (parseOptional(SyntaxKind.BarToken)) {
|
|
types.push(parseJSDocType());
|
|
}
|
|
|
|
types.end = scanner.getStartPos();
|
|
return types;
|
|
}
|
|
|
|
function parseJSDocAllType(): JSDocAllType {
|
|
let result = <JSDocAllType>createNode(SyntaxKind.JSDocAllType);
|
|
nextToken();
|
|
return finishNode(result);
|
|
}
|
|
|
|
function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType {
|
|
let pos = scanner.getStartPos();
|
|
// skip the ?
|
|
nextToken();
|
|
|
|
// Need to lookahead to decide if this is a nullable or unknown type.
|
|
|
|
// Here are cases where we'll pick the unknown type:
|
|
//
|
|
// Foo(?,
|
|
// { a: ? }
|
|
// Foo(?)
|
|
// Foo<?>
|
|
// Foo(?=
|
|
// (?|
|
|
if (token === SyntaxKind.CommaToken ||
|
|
token === SyntaxKind.CloseBraceToken ||
|
|
token === SyntaxKind.CloseParenToken ||
|
|
token === SyntaxKind.GreaterThanToken ||
|
|
token === SyntaxKind.EqualsToken ||
|
|
token === SyntaxKind.BarToken) {
|
|
|
|
let result = <JSDocUnknownType>createNode(SyntaxKind.JSDocUnknownType, pos);
|
|
return finishNode(result);
|
|
}
|
|
else {
|
|
let result = <JSDocNullableType>createNode(SyntaxKind.JSDocNullableType, pos);
|
|
result.type = parseJSDocType();
|
|
return finishNode(result);
|
|
}
|
|
}
|
|
|
|
export function parseIsolatedJSDocComment(content: string, start: number, length: number) {
|
|
initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined);
|
|
let jsDocComment = parseJSDocComment(/*parent:*/ undefined, start, length);
|
|
let diagnostics = parseDiagnostics;
|
|
clearState();
|
|
|
|
return jsDocComment ? { jsDocComment, diagnostics } : undefined;
|
|
}
|
|
|
|
export function parseJSDocComment(parent: Node, start: number, length: number): JSDocComment {
|
|
let comment = parseJSDocCommentWorker(start, length);
|
|
if (comment) {
|
|
fixupParentReferences(comment);
|
|
comment.parent = parent;
|
|
}
|
|
|
|
return comment;
|
|
}
|
|
|
|
export function parseJSDocCommentWorker(start: number, length: number): JSDocComment {
|
|
let content = sourceText;
|
|
start = start || 0;
|
|
let end = length === undefined ? content.length : start + length;
|
|
length = end - start;
|
|
|
|
Debug.assert(start >= 0);
|
|
Debug.assert(start <= end);
|
|
Debug.assert(end <= content.length);
|
|
|
|
let tags: NodeArray<JSDocTag>;
|
|
let pos: number;
|
|
|
|
// NOTE(cyrusn): This is essentially a handwritten scanner for JSDocComments. I
|
|
// considered using an actual Scanner, but this would complicate things. The
|
|
// scanner would need to know it was in a Doc Comment. Otherwise, it would then
|
|
// produce comments *inside* the doc comment. In the end it was just easier to
|
|
// write a simple scanner rather than go that route.
|
|
if (length >= "/** */".length) {
|
|
if (content.charCodeAt(start) === CharacterCodes.slash &&
|
|
content.charCodeAt(start + 1) === CharacterCodes.asterisk &&
|
|
content.charCodeAt(start + 2) === CharacterCodes.asterisk &&
|
|
content.charCodeAt(start + 3) !== CharacterCodes.asterisk) {
|
|
|
|
// Initially we can parse out a tag. We also have seen a starting asterisk.
|
|
// This is so that /** * @type */ doesn't parse.
|
|
let canParseTag = true;
|
|
let seenAsterisk = true;
|
|
|
|
for (pos = start + "/**".length; pos < end;) {
|
|
let ch = content.charCodeAt(pos);
|
|
pos++;
|
|
|
|
if (ch === CharacterCodes.at && canParseTag) {
|
|
parseTag();
|
|
|
|
// Once we parse out a tag, we cannot keep parsing out tags on this line.
|
|
canParseTag = false;
|
|
continue;
|
|
}
|
|
|
|
if (isLineBreak(ch)) {
|
|
// After a line break, we can parse a tag, and we haven't seen as asterisk
|
|
// on the next line yet.
|
|
canParseTag = true;
|
|
seenAsterisk = false;
|
|
continue;
|
|
}
|
|
|
|
if (isWhiteSpace(ch)) {
|
|
// Whitespace doesn't affect any of our parsing.
|
|
continue;
|
|
}
|
|
|
|
// Ignore the first asterisk on a line.
|
|
if (ch === CharacterCodes.asterisk) {
|
|
if (seenAsterisk) {
|
|
// If we've already seen an asterisk, then we can no longer parse a tag
|
|
// on this line.
|
|
canParseTag = false;
|
|
}
|
|
seenAsterisk = true;
|
|
continue;
|
|
}
|
|
|
|
// Anything else is doc comment text. We can't do anything with it. Because it
|
|
// wasn't a tag, we can no longer parse a tag on this line until we hit the next
|
|
// line break.
|
|
canParseTag = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return createJSDocComment();
|
|
|
|
function createJSDocComment(): JSDocComment {
|
|
if (!tags) {
|
|
return undefined;
|
|
}
|
|
|
|
let result = <JSDocComment>createNode(SyntaxKind.JSDocComment, start);
|
|
result.tags = tags;
|
|
return finishNode(result, end);
|
|
}
|
|
|
|
function skipWhitespace(): void {
|
|
while (pos < end && isWhiteSpace(content.charCodeAt(pos))) {
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
function parseTag(): void {
|
|
Debug.assert(content.charCodeAt(pos - 1) === CharacterCodes.at);
|
|
let atToken = createNode(SyntaxKind.AtToken, pos - 1);
|
|
atToken.end = pos;
|
|
|
|
let startPos = pos;
|
|
let tagName = scanIdentifier();
|
|
if (!tagName) {
|
|
return;
|
|
}
|
|
|
|
let tag = handleTag(atToken, tagName) || handleUnknownTag(atToken, tagName);
|
|
addTag(tag);
|
|
}
|
|
|
|
function handleTag(atToken: Node, tagName: Identifier): JSDocTag {
|
|
if (tagName) {
|
|
switch (tagName.text) {
|
|
case "param":
|
|
return handleParamTag(atToken, tagName);
|
|
case "return":
|
|
case "returns":
|
|
return handleReturnTag(atToken, tagName);
|
|
case "template":
|
|
return handleTemplateTag(atToken, tagName);
|
|
case "type":
|
|
return handleTypeTag(atToken, tagName);
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
function handleUnknownTag(atToken: Node, tagName: Identifier) {
|
|
let result = <JSDocTag>createNode(SyntaxKind.JSDocTag, atToken.pos);
|
|
result.atToken = atToken;
|
|
result.tagName = tagName;
|
|
return finishNode(result, pos);
|
|
}
|
|
|
|
function addTag(tag: JSDocTag): void {
|
|
if (tag) {
|
|
if (!tags) {
|
|
tags = <NodeArray<JSDocTag>>[];
|
|
tags.pos = tag.pos;
|
|
}
|
|
|
|
tags.push(tag);
|
|
tags.end = tag.end;
|
|
}
|
|
}
|
|
|
|
function tryParseTypeExpression(): JSDocTypeExpression {
|
|
skipWhitespace();
|
|
|
|
if (content.charCodeAt(pos) !== CharacterCodes.openBrace) {
|
|
return undefined;
|
|
}
|
|
|
|
let typeExpression = parseJSDocTypeExpression(pos, end - pos);
|
|
pos = typeExpression.end;
|
|
return typeExpression;
|
|
}
|
|
|
|
function handleParamTag(atToken: Node, tagName: Identifier) {
|
|
let typeExpression = tryParseTypeExpression();
|
|
|
|
skipWhitespace();
|
|
let name: Identifier;
|
|
let isBracketed: boolean;
|
|
if (content.charCodeAt(pos) === CharacterCodes.openBracket) {
|
|
pos++;
|
|
skipWhitespace();
|
|
name = scanIdentifier();
|
|
isBracketed = true;
|
|
}
|
|
else {
|
|
name = scanIdentifier();
|
|
}
|
|
|
|
if (!name) {
|
|
parseErrorAtPosition(pos, 0, Diagnostics.Identifier_expected);
|
|
return undefined;
|
|
}
|
|
|
|
let preName: Identifier, postName: Identifier;
|
|
if (typeExpression) {
|
|
postName = name;
|
|
}
|
|
else {
|
|
preName = name;
|
|
}
|
|
|
|
if (!typeExpression) {
|
|
typeExpression = tryParseTypeExpression();
|
|
}
|
|
|
|
let result = <JSDocParameterTag>createNode(SyntaxKind.JSDocParameterTag, atToken.pos);
|
|
result.atToken = atToken;
|
|
result.tagName = tagName;
|
|
result.preParameterName = preName;
|
|
result.typeExpression = typeExpression;
|
|
result.postParameterName = postName;
|
|
result.isBracketed = isBracketed;
|
|
return finishNode(result, pos);
|
|
}
|
|
|
|
function handleReturnTag(atToken: Node, tagName: Identifier): JSDocReturnTag {
|
|
if (forEach(tags, t => t.kind === SyntaxKind.JSDocReturnTag)) {
|
|
parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
|
|
}
|
|
|
|
let result = <JSDocReturnTag>createNode(SyntaxKind.JSDocReturnTag, atToken.pos);
|
|
result.atToken = atToken;
|
|
result.tagName = tagName;
|
|
result.typeExpression = tryParseTypeExpression();
|
|
return finishNode(result, pos);
|
|
}
|
|
|
|
function handleTypeTag(atToken: Node, tagName: Identifier): JSDocTypeTag {
|
|
if (forEach(tags, t => t.kind === SyntaxKind.JSDocTypeTag)) {
|
|
parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
|
|
}
|
|
|
|
let result = <JSDocTypeTag>createNode(SyntaxKind.JSDocTypeTag, atToken.pos);
|
|
result.atToken = atToken;
|
|
result.tagName = tagName;
|
|
result.typeExpression = tryParseTypeExpression();
|
|
return finishNode(result, pos);
|
|
}
|
|
|
|
function handleTemplateTag(atToken: Node, tagName: Identifier): JSDocTemplateTag {
|
|
if (forEach(tags, t => t.kind === SyntaxKind.JSDocTemplateTag)) {
|
|
parseErrorAtPosition(tagName.pos, pos - tagName.pos, Diagnostics._0_tag_already_specified, tagName.text);
|
|
}
|
|
|
|
let typeParameters = <NodeArray<TypeParameterDeclaration>>[];
|
|
typeParameters.pos = pos;
|
|
|
|
while (true) {
|
|
skipWhitespace();
|
|
|
|
let startPos = pos;
|
|
let name = scanIdentifier();
|
|
if (!name) {
|
|
parseErrorAtPosition(startPos, 0, Diagnostics.Identifier_expected);
|
|
return undefined;
|
|
}
|
|
|
|
let typeParameter = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter, name.pos);
|
|
typeParameter.name = name;
|
|
finishNode(typeParameter, pos);
|
|
|
|
typeParameters.push(typeParameter);
|
|
|
|
skipWhitespace();
|
|
if (content.charCodeAt(pos) !== CharacterCodes.comma) {
|
|
break;
|
|
}
|
|
|
|
pos++;
|
|
}
|
|
|
|
typeParameters.end = pos;
|
|
|
|
let result = <JSDocTemplateTag>createNode(SyntaxKind.JSDocTemplateTag, atToken.pos);
|
|
result.atToken = atToken;
|
|
result.tagName = tagName;
|
|
result.typeParameters = typeParameters;
|
|
return finishNode(result, pos);
|
|
}
|
|
|
|
function scanIdentifier(): Identifier {
|
|
let startPos = pos;
|
|
for (; pos < end; pos++) {
|
|
let ch = content.charCodeAt(pos);
|
|
if (pos === startPos && isIdentifierStart(ch, ScriptTarget.Latest)) {
|
|
continue;
|
|
}
|
|
else if (pos > startPos && isIdentifierPart(ch, ScriptTarget.Latest)) {
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (startPos === pos) {
|
|
return undefined;
|
|
}
|
|
|
|
let result = <Identifier>createNode(SyntaxKind.Identifier, startPos);
|
|
result.text = content.substring(startPos, pos);
|
|
return finishNode(result, pos);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module IncrementalParser {
|
|
export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean): SourceFile {
|
|
aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive);
|
|
|
|
checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks);
|
|
if (textChangeRangeIsUnchanged(textChangeRange)) {
|
|
// if the text didn't change, then we can just return our current source file as-is.
|
|
return sourceFile;
|
|
}
|
|
|
|
if (sourceFile.statements.length === 0) {
|
|
// If we don't have any statements in the current source file, then there's no real
|
|
// way to incrementally parse. So just do a full parse instead.
|
|
return Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setNodeParents*/ true)
|
|
}
|
|
|
|
// Make sure we're not trying to incrementally update a source file more than once. Once
|
|
// we do an update the original source file is considered unusbale from that point onwards.
|
|
//
|
|
// This is because we do incremental parsing in-place. i.e. we take nodes from the old
|
|
// tree and give them new positions and parents. From that point on, trusting the old
|
|
// tree at all is not possible as far too much of it may violate invariants.
|
|
let incrementalSourceFile = <IncrementalNode><Node>sourceFile;
|
|
Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed);
|
|
incrementalSourceFile.hasBeenIncrementallyParsed = true;
|
|
|
|
let oldText = sourceFile.text;
|
|
let syntaxCursor = createSyntaxCursor(sourceFile);
|
|
|
|
// Make the actual change larger so that we know to reparse anything whose lookahead
|
|
// might have intersected the change.
|
|
let changeRange = extendToAffectedRange(sourceFile, textChangeRange);
|
|
checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks);
|
|
|
|
// Ensure that extending the affected range only moved the start of the change range
|
|
// earlier in the file.
|
|
Debug.assert(changeRange.span.start <= textChangeRange.span.start);
|
|
Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span));
|
|
Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange)));
|
|
|
|
// The is the amount the nodes after the edit range need to be adjusted. It can be
|
|
// positive (if the edit added characters), negative (if the edit deleted characters)
|
|
// or zero (if this was a pure overwrite with nothing added/removed).
|
|
let delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length;
|
|
|
|
// If we added or removed characters during the edit, then we need to go and adjust all
|
|
// the nodes after the edit. Those nodes may move forward (if we inserted chars) or they
|
|
// may move backward (if we deleted chars).
|
|
//
|
|
// Doing this helps us out in two ways. First, it means that any nodes/tokens we want
|
|
// to reuse are already at the appropriate position in the new text. That way when we
|
|
// reuse them, we don't have to figure out if they need to be adjusted. Second, it makes
|
|
// it very easy to determine if we can reuse a node. If the node's position is at where
|
|
// we are in the text, then we can reuse it. Otherwise we can't. If the node's position
|
|
// is ahead of us, then we'll need to rescan tokens. If the node's position is behind
|
|
// us, then we'll need to skip it or crumble it as appropriate
|
|
//
|
|
// We will also adjust the positions of nodes that intersect the change range as well.
|
|
// By doing this, we ensure that all the positions in the old tree are consistent, not
|
|
// just the positions of nodes entirely before/after the change range. By being
|
|
// consistent, we can then easily map from positions to nodes in the old tree easily.
|
|
//
|
|
// Also, mark any syntax elements that intersect the changed span. We know, up front,
|
|
// that we cannot reuse these elements.
|
|
updateTokenPositionsAndMarkElements(incrementalSourceFile,
|
|
changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks);
|
|
|
|
// Now that we've set up our internal incremental state just proceed and parse the
|
|
// source file in the normal fashion. When possible the parser will retrieve and
|
|
// reuse nodes from the old tree.
|
|
//
|
|
// Note: passing in 'true' for setNodeParents is very important. When incrementally
|
|
// parsing, we will be reusing nodes from the old tree, and placing it into new
|
|
// parents. If we don't set the parents now, we'll end up with an observably
|
|
// inconsistent tree. Setting the parents on the new tree should be very fast. We
|
|
// will immediately bail out of walking any subtrees when we can see that their parents
|
|
// are already correct.
|
|
let result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /* setParentNode */ true)
|
|
|
|
return result;
|
|
}
|
|
|
|
function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) {
|
|
if (isArray) {
|
|
visitArray(<IncrementalNodeArray>element);
|
|
}
|
|
else {
|
|
visitNode(<IncrementalNode>element);
|
|
}
|
|
return;
|
|
|
|
function visitNode(node: IncrementalNode) {
|
|
if (aggressiveChecks && shouldCheckNode(node)) {
|
|
var text = oldText.substring(node.pos, node.end);
|
|
}
|
|
|
|
// Ditch any existing LS children we may have created. This way we can avoid
|
|
// moving them forward.
|
|
if (node._children) {
|
|
node._children = undefined;
|
|
}
|
|
|
|
if (node.jsDocComment) {
|
|
node.jsDocComment = undefined;
|
|
}
|
|
|
|
node.pos += delta;
|
|
node.end += delta;
|
|
|
|
if (aggressiveChecks && shouldCheckNode(node)) {
|
|
Debug.assert(text === newText.substring(node.pos, node.end));
|
|
}
|
|
|
|
forEachChild(node, visitNode, visitArray);
|
|
checkNodePositions(node, aggressiveChecks);
|
|
}
|
|
|
|
function visitArray(array: IncrementalNodeArray) {
|
|
array._children = undefined;
|
|
array.pos += delta;
|
|
array.end += delta;
|
|
|
|
for (let node of array) {
|
|
visitNode(node);
|
|
}
|
|
}
|
|
}
|
|
|
|
function shouldCheckNode(node: Node) {
|
|
switch (node.kind) {
|
|
case SyntaxKind.StringLiteral:
|
|
case SyntaxKind.NumericLiteral:
|
|
case SyntaxKind.Identifier:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) {
|
|
Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range");
|
|
Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range");
|
|
Debug.assert(element.pos <= element.end);
|
|
|
|
// We have an element that intersects the change range in some way. It may have its
|
|
// start, or its end (or both) in the changed range. We want to adjust any part
|
|
// that intersects such that the final tree is in a consistent state. i.e. all
|
|
// chlidren have spans within the span of their parent, and all siblings are ordered
|
|
// properly.
|
|
|
|
// We may need to update both the 'pos' and the 'end' of the element.
|
|
|
|
// If the 'pos' is before the start of the change, then we don't need to touch it.
|
|
// If it isn't, then the 'pos' must be inside the change. How we update it will
|
|
// depend if delta is positive or negative. If delta is positive then we have
|
|
// something like:
|
|
//
|
|
// -------------------AAA-----------------
|
|
// -------------------BBBCCCCCCC-----------------
|
|
//
|
|
// In this case, we consider any node that started in the change range to still be
|
|
// starting at the same position.
|
|
//
|
|
// however, if the delta is negative, then we instead have something like this:
|
|
//
|
|
// -------------------XXXYYYYYYY-----------------
|
|
// -------------------ZZZ-----------------
|
|
//
|
|
// In this case, any element that started in the 'X' range will keep its position.
|
|
// However any element htat started after that will have their pos adjusted to be
|
|
// at the end of the new range. i.e. any node that started in the 'Y' range will
|
|
// be adjusted to have their start at the end of the 'Z' range.
|
|
//
|
|
// The element will keep its position if possible. Or Move backward to the new-end
|
|
// if it's in the 'Y' range.
|
|
element.pos = Math.min(element.pos, changeRangeNewEnd);
|
|
|
|
// If the 'end' is after the change range, then we always adjust it by the delta
|
|
// amount. However, if the end is in the change range, then how we adjust it
|
|
// will depend on if delta is positive or negative. If delta is positive then we
|
|
// have something like:
|
|
//
|
|
// -------------------AAA-----------------
|
|
// -------------------BBBCCCCCCC-----------------
|
|
//
|
|
// In this case, we consider any node that ended inside the change range to keep its
|
|
// end position.
|
|
//
|
|
// however, if the delta is negative, then we instead have something like this:
|
|
//
|
|
// -------------------XXXYYYYYYY-----------------
|
|
// -------------------ZZZ-----------------
|
|
//
|
|
// In this case, any element that ended in the 'X' range will keep its position.
|
|
// However any element htat ended after that will have their pos adjusted to be
|
|
// at the end of the new range. i.e. any node that ended in the 'Y' range will
|
|
// be adjusted to have their end at the end of the 'Z' range.
|
|
if (element.end >= changeRangeOldEnd) {
|
|
// Element ends after the change range. Always adjust the end pos.
|
|
element.end += delta;
|
|
}
|
|
else {
|
|
// Element ends in the change range. The element will keep its position if
|
|
// possible. Or Move backward to the new-end if it's in the 'Y' range.
|
|
element.end = Math.min(element.end, changeRangeNewEnd);
|
|
}
|
|
|
|
Debug.assert(element.pos <= element.end);
|
|
if (element.parent) {
|
|
Debug.assert(element.pos >= element.parent.pos);
|
|
Debug.assert(element.end <= element.parent.end);
|
|
}
|
|
}
|
|
|
|
function checkNodePositions(node: Node, aggressiveChecks: boolean) {
|
|
if (aggressiveChecks) {
|
|
let pos = node.pos;
|
|
forEachChild(node, child => {
|
|
Debug.assert(child.pos >= pos);
|
|
pos = child.end;
|
|
});
|
|
Debug.assert(pos <= node.end);
|
|
}
|
|
}
|
|
|
|
function updateTokenPositionsAndMarkElements(
|
|
sourceFile: IncrementalNode,
|
|
changeStart: number,
|
|
changeRangeOldEnd: number,
|
|
changeRangeNewEnd: number,
|
|
delta: number,
|
|
oldText: string,
|
|
newText: string,
|
|
aggressiveChecks: boolean): void {
|
|
|
|
visitNode(sourceFile);
|
|
return;
|
|
|
|
function visitNode(child: IncrementalNode) {
|
|
Debug.assert(child.pos <= child.end);
|
|
if (child.pos > changeRangeOldEnd) {
|
|
// Node is entirely past the change range. We need to move both its pos and
|
|
// end, forward or backward appropriately.
|
|
moveElementEntirelyPastChangeRange(child, /*isArray*/ false, delta, oldText, newText, aggressiveChecks);
|
|
return;
|
|
}
|
|
|
|
// Check if the element intersects the change range. If it does, then it is not
|
|
// reusable. Also, we'll need to recurse to see what constituent portions we may
|
|
// be able to use.
|
|
let fullEnd = child.end;
|
|
if (fullEnd >= changeStart) {
|
|
child.intersectsChange = true;
|
|
child._children = undefined;
|
|
|
|
// Adjust the pos or end (or both) of the intersecting element accordingly.
|
|
adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta);
|
|
forEachChild(child, visitNode, visitArray);
|
|
|
|
checkNodePositions(child, aggressiveChecks);
|
|
return;
|
|
}
|
|
|
|
// Otherwise, the node is entirely before the change range. No need to do anything with it.
|
|
Debug.assert(fullEnd < changeStart);
|
|
}
|
|
|
|
function visitArray(array: IncrementalNodeArray) {
|
|
Debug.assert(array.pos <= array.end);
|
|
if (array.pos > changeRangeOldEnd) {
|
|
// Array is entirely after the change range. We need to move it, and move any of
|
|
// its children.
|
|
moveElementEntirelyPastChangeRange(array, /*isArray*/ true, delta, oldText, newText, aggressiveChecks);
|
|
return;
|
|
}
|
|
|
|
// Check if the element intersects the change range. If it does, then it is not
|
|
// reusable. Also, we'll need to recurse to see what constituent portions we may
|
|
// be able to use.
|
|
let fullEnd = array.end;
|
|
if (fullEnd >= changeStart) {
|
|
array.intersectsChange = true;
|
|
array._children = undefined;
|
|
|
|
// Adjust the pos or end (or both) of the intersecting array accordingly.
|
|
adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta);
|
|
for (let node of array) {
|
|
visitNode(node);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Otherwise, the array is entirely before the change range. No need to do anything with it.
|
|
Debug.assert(fullEnd < changeStart);
|
|
}
|
|
}
|
|
|
|
function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange {
|
|
// Consider the following code:
|
|
// void foo() { /; }
|
|
//
|
|
// If the text changes with an insertion of / just before the semicolon then we end up with:
|
|
// void foo() { //; }
|
|
//
|
|
// If we were to just use the changeRange a is, then we would not rescan the { token
|
|
// (as it does not intersect the actual original change range). Because an edit may
|
|
// change the token touching it, we actually need to look back *at least* one token so
|
|
// that the prior token sees that change.
|
|
let maxLookahead = 1;
|
|
|
|
let start = changeRange.span.start;
|
|
|
|
// the first iteration aligns us with the change start. subsequent iteration move us to
|
|
// the left by maxLookahead tokens. We only need to do this as long as we're not at the
|
|
// start of the tree.
|
|
for (let i = 0; start > 0 && i <= maxLookahead; i++) {
|
|
let nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start);
|
|
Debug.assert(nearestNode.pos <= start);
|
|
let position = nearestNode.pos;
|
|
|
|
start = Math.max(0, position - 1);
|
|
}
|
|
|
|
let finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span));
|
|
let finalLength = changeRange.newLength + (changeRange.span.start - start);
|
|
|
|
return createTextChangeRange(finalSpan, finalLength);
|
|
}
|
|
|
|
function findNearestNodeStartingBeforeOrAtPosition(sourceFile: SourceFile, position: number): Node {
|
|
let bestResult: Node = sourceFile;
|
|
let lastNodeEntirelyBeforePosition: Node;
|
|
|
|
forEachChild(sourceFile, visit);
|
|
|
|
if (lastNodeEntirelyBeforePosition) {
|
|
let lastChildOfLastEntireNodeBeforePosition = getLastChild(lastNodeEntirelyBeforePosition);
|
|
if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) {
|
|
bestResult = lastChildOfLastEntireNodeBeforePosition;
|
|
}
|
|
}
|
|
|
|
return bestResult;
|
|
|
|
function getLastChild(node: Node): Node {
|
|
while (true) {
|
|
let lastChild = getLastChildWorker(node);
|
|
if (lastChild) {
|
|
node = lastChild;
|
|
}
|
|
else {
|
|
return node;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getLastChildWorker(node: Node): Node {
|
|
let last: Node = undefined;
|
|
forEachChild(node, child => {
|
|
if (nodeIsPresent(child)) {
|
|
last = child;
|
|
}
|
|
});
|
|
return last;
|
|
}
|
|
|
|
function visit(child: Node) {
|
|
if (nodeIsMissing(child)) {
|
|
// Missing nodes are effectively invisible to us. We never even consider them
|
|
// When trying to find the nearest node before us.
|
|
return;
|
|
}
|
|
|
|
// If the child intersects this position, then this node is currently the nearest
|
|
// node that starts before the position.
|
|
if (child.pos <= position) {
|
|
if (child.pos >= bestResult.pos) {
|
|
// This node starts before the position, and is closer to the position than
|
|
// the previous best node we found. It is now the new best node.
|
|
bestResult = child;
|
|
}
|
|
|
|
// Now, the node may overlap the position, or it may end entirely before the
|
|
// position. If it overlaps with the position, then either it, or one of its
|
|
// children must be the nearest node before the position. So we can just
|
|
// recurse into this child to see if we can find something better.
|
|
if (position < child.end) {
|
|
// The nearest node is either this child, or one of the children inside
|
|
// of it. We've already marked this child as the best so far. Recurse
|
|
// in case one of the children is better.
|
|
forEachChild(child, visit);
|
|
|
|
// Once we look at the children of this node, then there's no need to
|
|
// continue any further.
|
|
return true;
|
|
}
|
|
else {
|
|
Debug.assert(child.end <= position);
|
|
// The child ends entirely before this position. Say you have the following
|
|
// (where $ is the position)
|
|
//
|
|
// <complex expr 1> ? <complex expr 2> $ : <...> <...>
|
|
//
|
|
// We would want to find the nearest preceding node in "complex expr 2".
|
|
// To support that, we keep track of this node, and once we're done searching
|
|
// for a best node, we recurse down this node to see if we can find a good
|
|
// result in it.
|
|
//
|
|
// This approach allows us to quickly skip over nodes that are entirely
|
|
// before the position, while still allowing us to find any nodes in the
|
|
// last one that might be what we want.
|
|
lastNodeEntirelyBeforePosition = child;
|
|
}
|
|
}
|
|
else {
|
|
Debug.assert(child.pos > position);
|
|
// We're now at a node that is entirely past the position we're searching for.
|
|
// This node (and all following nodes) could never contribute to the result,
|
|
// so just skip them by returning 'true' here.
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) {
|
|
let oldText = sourceFile.text;
|
|
if (textChangeRange) {
|
|
Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length);
|
|
|
|
if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
|
|
let oldTextPrefix = oldText.substr(0, textChangeRange.span.start);
|
|
let newTextPrefix = newText.substr(0, textChangeRange.span.start);
|
|
Debug.assert(oldTextPrefix === newTextPrefix);
|
|
|
|
let oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length);
|
|
let newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length);
|
|
Debug.assert(oldTextSuffix === newTextSuffix);
|
|
}
|
|
}
|
|
}
|
|
|
|
interface IncrementalElement extends TextRange {
|
|
parent?: Node;
|
|
intersectsChange: boolean
|
|
length?: number;
|
|
_children: Node[];
|
|
}
|
|
|
|
export interface IncrementalNode extends Node, IncrementalElement {
|
|
hasBeenIncrementallyParsed: boolean
|
|
}
|
|
|
|
interface IncrementalNodeArray extends NodeArray<IncrementalNode>, IncrementalElement {
|
|
length: number
|
|
}
|
|
|
|
// Allows finding nodes in the source file at a certain position in an efficient manner.
|
|
// The implementation takes advantage of the calling pattern it knows the parser will
|
|
// make in order to optimize finding nodes as quickly as possible.
|
|
export interface SyntaxCursor {
|
|
currentNode(position: number): IncrementalNode;
|
|
}
|
|
|
|
function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor {
|
|
let currentArray: NodeArray<Node> = sourceFile.statements;
|
|
let currentArrayIndex = 0;
|
|
|
|
Debug.assert(currentArrayIndex < currentArray.length);
|
|
let current = currentArray[currentArrayIndex];
|
|
let lastQueriedPosition = InvalidPosition.Value;
|
|
|
|
return {
|
|
currentNode(position: number) {
|
|
// Only compute the current node if the position is different than the last time
|
|
// we were asked. The parser commonly asks for the node at the same position
|
|
// twice. Once to know if can read an appropriate list element at a certain point,
|
|
// and then to actually read and consume the node.
|
|
if (position !== lastQueriedPosition) {
|
|
// Much of the time the parser will need the very next node in the array that
|
|
// we just returned a node from.So just simply check for that case and move
|
|
// forward in the array instead of searching for the node again.
|
|
if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) {
|
|
currentArrayIndex++;
|
|
current = currentArray[currentArrayIndex];
|
|
}
|
|
|
|
// If we don't have a node, or the node we have isn't in the right position,
|
|
// then try to find a viable node at the position requested.
|
|
if (!current || current.pos !== position) {
|
|
findHighestListElementThatStartsAtPosition(position);
|
|
}
|
|
}
|
|
|
|
// Cache this query so that we don't do any extra work if the parser calls back
|
|
// into us. Note: this is very common as the parser will make pairs of calls like
|
|
// 'isListElement -> parseListElement'. If we were unable to find a node when
|
|
// called with 'isListElement', we don't want to redo the work when parseListElement
|
|
// is called immediately after.
|
|
lastQueriedPosition = position;
|
|
|
|
// Either we don'd have a node, or we have a node at the position being asked for.
|
|
Debug.assert(!current || current.pos === position);
|
|
return <IncrementalNode>current;
|
|
}
|
|
};
|
|
|
|
// Finds the highest element in the tree we can find that starts at the provided position.
|
|
// The element must be a direct child of some node list in the tree. This way after we
|
|
// return it, we can easily return its next sibling in the list.
|
|
function findHighestListElementThatStartsAtPosition(position: number) {
|
|
// Clear out any cached state about the last node we found.
|
|
currentArray = undefined;
|
|
currentArrayIndex = InvalidPosition.Value;
|
|
current = undefined;
|
|
|
|
// Recurse into the source file to find the highest node at this position.
|
|
forEachChild(sourceFile, visitNode, visitArray);
|
|
return;
|
|
|
|
function visitNode(node: Node) {
|
|
if (position >= node.pos && position < node.end) {
|
|
// Position was within this node. Keep searching deeper to find the node.
|
|
forEachChild(node, visitNode, visitArray);
|
|
|
|
// don't procede any futher in the search.
|
|
return true;
|
|
}
|
|
|
|
// position wasn't in this node, have to keep searching.
|
|
return false;
|
|
}
|
|
|
|
function visitArray(array: NodeArray<Node>) {
|
|
if (position >= array.pos && position < array.end) {
|
|
// position was in this array. Search through this array to see if we find a
|
|
// viable element.
|
|
for (let i = 0, n = array.length; i < n; i++) {
|
|
let child = array[i];
|
|
if (child) {
|
|
if (child.pos === position) {
|
|
// Found the right node. We're done.
|
|
currentArray = array;
|
|
currentArrayIndex = i;
|
|
current = child;
|
|
return true;
|
|
}
|
|
else {
|
|
if (child.pos < position && position < child.end) {
|
|
// Position in somewhere within this child. Search in it and
|
|
// stop searching in this array.
|
|
forEachChild(child, visitNode, visitArray);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// position wasn't in this array, have to keep searching.
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
const enum InvalidPosition {
|
|
Value = -1
|
|
}
|
|
}
|
|
}
|