import * as ts from "./_namespaces/ts"; import { AccessorDeclaration, addRange, addRelatedInfo, AmdDependency, AnnotationDeclaration, AnnotationElement, AnnotationPropertyDeclaration, append, ArrayBindingElement, ArrayBindingPattern, ArrayLiteralExpression, ArrayTypeNode, ArrowFunction, AsExpression, AssertClause, AssertEntry, AssertionLevel, AsteriskToken, attachFileToDiagnostics, AwaitExpression, BaseNodeFactory, BinaryExpression, BinaryOperatorToken, BindingElement, BindingName, BindingPattern, Block, BooleanLiteral, BreakOrContinueStatement, BreakStatement, CallExpression, CallSignatureDeclaration, canHaveModifiers, CaseBlock, CaseClause, CaseOrDefaultClause, CatchClause, CharacterCodes, CheckJsDirective, ClassDeclaration, ClassElement, ClassExpression, ClassLikeDeclaration, ClassStaticBlockDeclaration, CommaListExpression, CommentDirective, commentPragmas, CommentRange, CompilerOptions, ComputedPropertyName, concatenate, ConditionalExpression, ConditionalTypeNode, ConstructorDeclaration, ConstructorTypeNode, ConstructSignatureDeclaration, containsParseError, ContinueStatement, convertToObjectWorker, createDetachedDiagnostic, createNodeFactory, createScanner, createTextChangeRange, createTextSpanFromBounds, Debug, Decorator, DefaultClause, defaultInitCompilerOptions, DeleteExpression, Diagnostic, DiagnosticMessage, Diagnostics, DiagnosticWithDetachedLocation, DoStatement, DotDotDotToken, ElementAccessExpression, emptyArray, emptyMap, EndOfFileToken, ensureScriptKind, EntityName, EnumDeclaration, EnumMember, ESMap, EtsComponentExpression, EtsFlags, ExclamationToken, ExportAssignment, ExportDeclaration, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, Extension, ExternalModuleReference, fileExtensionIsOneOf, FileReference, findIndex, forEach, ForEachChildNodes, ForInOrOfStatement, ForInStatement, ForOfStatement, ForStatement, FunctionDeclaration, FunctionExpression, FunctionOrConstructorTypeNode, FunctionTypeNode, GetAccessorDeclaration, getBinaryOperatorPrecedence, getEtsExtendDecoratorsComponentNames, getEtsStylesDecoratorComponentNames, getFullWidth, getJSDocCommentRanges, getLanguageVariant, getLastChild, getLeadingCommentRanges, getModifiers, getPropertyNameForPropertyNameNode, getRootComponent, getSdkPath, getSpellingSuggestion, getTextOfNodeFromSourceText, getTextOfPropertyName, getVirtualEtsComponent, hasEtsBuilderDecoratorNames, hasEtsExtendDecoratorNames, hasEtsStylesDecoratorNames, HasJSDoc, hasJSDocNodes, HasModifiers, HeritageClause, Identifier, idText, IfStatement, ImportClause, ImportDeclaration, ImportEqualsDeclaration, ImportOrExportSpecifier, ImportSpecifier, ImportTypeAssertionContainer, ImportTypeNode, IndexedAccessTypeNode, IndexSignatureDeclaration, InferTypeNode, InterfaceDeclaration, IntersectionTypeNode, isArray, isAssignmentOperator, isAsyncModifier, isCallExpression, isClassMemberModifier, isExportAssignment, isExportDeclaration, isExportModifier, isExpressionWithTypeArguments, isExternalModuleReference, isFunctionTypeNode, isIdentifierText, isImportDeclaration, isImportEqualsDeclaration, isJSDocFunctionType, isJSDocNullableType, isJSDocReturnTag, isJSDocTypeTag, isJsxOpeningElement, isJsxOpeningFragment, isKeyword, isLeftHandSideExpression, isLiteralKind, isMetaProperty, isModifierKind, isNonNullExpression, isPrivateIdentifier, isPropertyAccessExpression, isSetAccessorDeclaration, isStringOrNumericLiteralLike, isTaggedTemplateExpression, isTemplateLiteralKind, isTokenInsideBuilder, isTypeReferenceNode, IterationStatement, JSDoc, JSDocAllType, JSDocAugmentsTag, JSDocAuthorTag, JSDocCallbackTag, JSDocClassTag, JSDocComment, JSDocContainer, JSDocDeprecatedTag, JSDocEnumTag, JSDocFunctionType, JSDocImplementsTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocMemberName, JSDocNameReference, JSDocNamespaceDeclaration, JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, JSDocOverrideTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyLikeTag, JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, JSDocSeeTag, JSDocSignature, JSDocSyntaxKind, JSDocTag, JSDocTemplateTag, JSDocText, JSDocThisTag, JSDocTypedefTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, JSDocUnknownTag, JSDocUnknownType, JSDocVariadicType, JsonMinusNumericLiteral, JsonObjectExpressionStatement, JsonSourceFile, JsxAttribute, JsxAttributes, JsxAttributeValue, JsxChild, JsxClosingElement, JsxClosingFragment, JsxElement, JsxExpression, JsxFragment, JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxSelfClosingElement, JsxSpreadAttribute, JsxTagNameExpression, JsxTagNamePropertyAccess, JsxText, JsxTokenSyntaxKind, LabeledStatement, LanguageVariant, last, lastOrUndefined, LeftHandSideExpression, LiteralExpression, LiteralLikeNode, LiteralTypeNode, map, Map, mapDefined, MappedTypeNode, MemberExpression, MemoryDotting, MetaProperty, MethodDeclaration, MethodSignature, MinusToken, MissingDeclaration, Modifier, ModifierFlags, ModifierLike, ModifiersArray, modifiersToFlags, ModuleBlock, ModuleDeclaration, ModuleKind, Mutable, NamedExportBindings, NamedExports, NamedImports, NamedImportsOrExports, NamedTupleMember, NamespaceDeclaration, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, Node, NodeArray, NodeFactoryFlags, NodeFlags, nodeIsMissing, nodeIsPresent, NonNullExpression, noop, normalizePath, NoSubstitutionTemplateLiteral, NullLiteral, NumericLiteral, objectAllocator, ObjectBindingPattern, ObjectLiteralElementLike, ObjectLiteralExpression, OperatorPrecedence, OptionalTypeNode, PackageJsonInfo, ParameterDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, PartiallyEmittedExpression, perfLogger, PlusToken, PostfixUnaryExpression, PostfixUnaryOperator, PragmaDefinition, PragmaKindFlags, PragmaMap, PragmaPseudoMap, PragmaPseudoMapEntry, PrefixUnaryExpression, PrefixUnaryOperator, PrimaryExpression, PrivateIdentifier, processKit, PropertyAccessEntityNameExpression, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyName, PropertySignature, QualifiedName, QuestionDotToken, QuestionToken, ReadonlyKeyword, ReadonlyPragmaMap, ReadonlyTextRange, RestTypeNode, ReturnStatement, SatisfiesExpression, ScriptKind, ScriptTarget, Set, SetAccessorDeclaration, setParent, setParentRecursive, setTextRange, setTextRangePos, setTextRangePosEnd, setTextRangePosWidth, ShorthandPropertyAssignment, skipTrivia, some, SourceFile, SpreadAssignment, SpreadElement, startsWith, Statement, StringLiteral, StructDeclaration, supportedDeclarationExtensions, SwitchStatement, SyntaxKind, TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteralToken, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateMiddle, TemplateSpan, TemplateTail, TextChangeRange, textChangeRangeIsUnchanged, textChangeRangeNewSpan, TextRange, textSpanEnd, textToKeywordObj, ThisExpression, ThisTypeNode, ThrowStatement, toArray, Token, TokenFlags, tokenIsIdentifierOrKeyword, tokenIsIdentifierOrKeywordOrGreaterThan, tokenToString, tracing, TransformFlags, trimString, TryStatement, TupleTypeNode, TypeAliasDeclaration, TypeAssertion, TypeElement, TypeLiteralNode, TypeNode, TypeOfExpression, TypeOperatorNode, TypeParameterDeclaration, TypePredicateNode, TypeQueryNode, TypeReferenceNode, UnaryExpression, UnionOrIntersectionTypeNode, UnionTypeNode, UpdateExpression, VariableDeclaration, VariableDeclarationList, VariableStatement, VoidExpression, WhileStatement, WithStatement, YieldExpression, } from "./_namespaces/ts"; import * as performance from "./_namespaces/ts.performance"; const enum SignatureFlags { None = 0, Yield = 1 << 0, Await = 1 << 1, Type = 1 << 2, IgnoreMissingOpenBrace = 1 << 4, JSDoc = 1 << 5, } const enum SpeculationKind { TryParse, Lookahead, Reparse } let NodeConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; let TokenConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; let IdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; let PrivateIdentifierConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos?: number, end?: number) => Node; /** * NOTE: You should not use this, it is only exported to support `createNode` in `~/src/deprecatedCompat/deprecations.ts`. * * @internal */ export const parseBaseNodeFactory: BaseNodeFactory = { createBaseSourceFileNode: kind => new (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor()))(kind, -1, -1), createBaseIdentifierNode: kind => new (IdentifierConstructor || (IdentifierConstructor = objectAllocator.getIdentifierConstructor()))(kind, -1, -1), createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1), createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1), createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1), }; /** @internal */ export const parseNodeFactory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules, parseBaseNodeFactory); function visitNode(cbNode: (node: Node) => T, node: Node | undefined): T | undefined { return node && cbNode(node); } function visitNodes(cbNode: (node: Node) => T, cbNodes: ((node: NodeArray) => T | undefined) | undefined, nodes: NodeArray | undefined): T | undefined { if (nodes) { if (cbNodes) { return cbNodes(nodes); } for (const node of nodes) { const result = cbNode(node); if (result) { return result; } } } } /** @internal */ export function isJSDocLikeText(text: string, start: number) { return text.charCodeAt(start + 1) === CharacterCodes.asterisk && text.charCodeAt(start + 2) === CharacterCodes.asterisk && text.charCodeAt(start + 3) !== CharacterCodes.slash; } /** @internal */ export function isFileProbablyExternalModule(sourceFile: SourceFile) { // Try to use the first top-level import/export when available, then // fall back to looking for an 'import.meta' somewhere in the tree if necessary. return forEach(sourceFile.statements, isAnExternalModuleIndicatorNode) || getImportMetaIfNecessary(sourceFile); } function isAnExternalModuleIndicatorNode(node: Node) { return canHaveModifiers(node) && hasModifierOfKind(node, SyntaxKind.ExportKeyword) || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) || isImportDeclaration(node) || isExportAssignment(node) || isExportDeclaration(node) ? node : undefined; } function getImportMetaIfNecessary(sourceFile: SourceFile) { return sourceFile.flags & NodeFlags.PossiblyContainsImportMeta ? walkTreeForImportMeta(sourceFile) : undefined; } function walkTreeForImportMeta(node: Node): Node | undefined { return isImportMeta(node) ? node : forEachChild(node, walkTreeForImportMeta); } /** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */ function hasModifierOfKind(node: HasModifiers, kind: SyntaxKind) { return some(node.modifiers, m => m.kind === kind); } function isImportMeta(node: Node): boolean { return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta"; } type ForEachChildFunction = (node: TNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined) => T | undefined; type ForEachChildTable = { [TNode in ForEachChildNodes as TNode["kind"]]: ForEachChildFunction }; const forEachChildTable: ForEachChildTable = { [SyntaxKind.QualifiedName]: function forEachChildInQualifiedName(node: QualifiedName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.left) || visitNode(cbNode, node.right); }, [SyntaxKind.TypeParameter]: function forEachChildInTypeParameter(node: TypeParameterDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.constraint) || visitNode(cbNode, node.default) || visitNode(cbNode, node.expression); }, [SyntaxKind.ShorthandPropertyAssignment]: function forEachChildInShorthandPropertyAssignment(node: ShorthandPropertyAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.exclamationToken) || visitNode(cbNode, node.equalsToken) || visitNode(cbNode, node.objectAssignmentInitializer); }, [SyntaxKind.SpreadAssignment]: function forEachChildInSpreadAssignment(node: SpreadAssignment, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.Parameter]: function forEachChildInParameter(node: ParameterDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.dotDotDotToken) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.type) || visitNode(cbNode, node.initializer); }, [SyntaxKind.PropertyDeclaration]: function forEachChildInPropertyDeclaration(node: PropertyDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.exclamationToken) || visitNode(cbNode, node.type) || visitNode(cbNode, node.initializer); }, [SyntaxKind.AnnotationPropertyDeclaration]: function forEachChildInAnnotationPropertyDeclaration(node: AnnotationPropertyDeclaration, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name) || visitNode(cbNode, node.type) || visitNode(cbNode, node.initializer); }, [SyntaxKind.PropertySignature]: function forEachChildInPropertySignature(node: PropertySignature, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.type) || visitNode(cbNode, node.initializer); }, [SyntaxKind.PropertyAssignment]: function forEachChildInPropertyAssignment(node: PropertyAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.exclamationToken) || visitNode(cbNode, node.initializer); }, [SyntaxKind.VariableDeclaration]: function forEachChildInVariableDeclaration(node: VariableDeclaration, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name) || visitNode(cbNode, node.exclamationToken) || visitNode(cbNode, node.type) || visitNode(cbNode, node.initializer); }, [SyntaxKind.BindingElement]: function forEachChildInBindingElement(node: BindingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.dotDotDotToken) || visitNode(cbNode, node.propertyName) || visitNode(cbNode, node.name) || visitNode(cbNode, node.initializer); }, [SyntaxKind.IndexSignature]: function forEachChildInIndexSignature(node: IndexSignatureDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type); }, [SyntaxKind.ConstructorType]: function forEachChildInConstructorType(node: ConstructorTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type); }, [SyntaxKind.FunctionType]: function forEachChildInFunctionType(node: FunctionTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type); }, [SyntaxKind.CallSignature]: forEachChildInCallOrConstructSignature, [SyntaxKind.ConstructSignature]: forEachChildInCallOrConstructSignature, [SyntaxKind.EtsComponentExpression]: function forEachChildInEtsComponentExpression(node: EtsComponentExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNodes(cbNode, cbNodes, node.arguments) || visitNode(cbNode, node.body); }, [SyntaxKind.MethodDeclaration]: function forEachChildInMethodDeclaration(node: MethodDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.asteriskToken) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.exclamationToken) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.body); }, [SyntaxKind.MethodSignature]: function forEachChildInMethodSignature(node: MethodSignature, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type); }, [SyntaxKind.Constructor]: function forEachChildInConstructor(node: ConstructorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.body); }, [SyntaxKind.GetAccessor]: function forEachChildInGetAccessor(node: GetAccessorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.body); }, [SyntaxKind.SetAccessor]: function forEachChildInSetAccessor(node: SetAccessorDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.body); }, [SyntaxKind.FunctionDeclaration]: function forEachChildInFunctionDeclaration(node: FunctionDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.asteriskToken) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.body); }, [SyntaxKind.FunctionExpression]: function forEachChildInFunctionExpression(node: FunctionExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.asteriskToken) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.body); }, [SyntaxKind.ArrowFunction]: function forEachChildInArrowFunction(node: ArrowFunction, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type) || visitNode(cbNode, node.equalsGreaterThanToken) || visitNode(cbNode, node.body); }, [SyntaxKind.ClassStaticBlockDeclaration]: function forEachChildInClassStaticBlockDeclaration(node: ClassStaticBlockDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.body); }, [SyntaxKind.TypeReference]: function forEachChildInTypeReference(node: TypeReferenceNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.typeName) || visitNodes(cbNode, cbNodes, node.typeArguments); }, [SyntaxKind.TypePredicate]: function forEachChildInTypePredicate(node: TypePredicateNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.assertsModifier) || visitNode(cbNode, node.parameterName) || visitNode(cbNode, node.type); }, [SyntaxKind.TypeQuery]: function forEachChildInTypeQuery(node: TypeQueryNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.exprName) || visitNodes(cbNode, cbNodes, node.typeArguments); }, [SyntaxKind.TypeLiteral]: function forEachChildInTypeLiteral(node: TypeLiteralNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.members); }, [SyntaxKind.ArrayType]: function forEachChildInArrayType(node: ArrayTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.elementType); }, [SyntaxKind.TupleType]: function forEachChildInTupleType(node: TupleTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); }, [SyntaxKind.UnionType]: forEachChildInUnionOrIntersectionType, [SyntaxKind.IntersectionType]: forEachChildInUnionOrIntersectionType, [SyntaxKind.ConditionalType]: function forEachChildInConditionalType(node: ConditionalTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.checkType) || visitNode(cbNode, node.extendsType) || visitNode(cbNode, node.trueType) || visitNode(cbNode, node.falseType); }, [SyntaxKind.InferType]: function forEachChildInInferType(node: InferTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.typeParameter); }, [SyntaxKind.ImportType]: function forEachChildInImportType(node: ImportTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.argument) || visitNode(cbNode, node.assertions) || visitNode(cbNode, node.qualifier) || visitNodes(cbNode, cbNodes, node.typeArguments); }, [SyntaxKind.ImportTypeAssertionContainer]: function forEachChildInImportTypeAssertionContainer(node: ImportTypeAssertionContainer, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.assertClause); }, [SyntaxKind.ParenthesizedType]: forEachChildInParenthesizedTypeOrTypeOperator, [SyntaxKind.TypeOperator]: forEachChildInParenthesizedTypeOrTypeOperator, [SyntaxKind.IndexedAccessType]: function forEachChildInIndexedAccessType(node: IndexedAccessTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.objectType) || visitNode(cbNode, node.indexType); }, [SyntaxKind.MappedType]: function forEachChildInMappedType(node: MappedTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.readonlyToken) || visitNode(cbNode, node.typeParameter) || visitNode(cbNode, node.nameType) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.type) || visitNodes(cbNode, cbNodes, node.members); }, [SyntaxKind.LiteralType]: function forEachChildInLiteralType(node: LiteralTypeNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.literal); }, [SyntaxKind.NamedTupleMember]: function forEachChildInNamedTupleMember(node: NamedTupleMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.dotDotDotToken) || visitNode(cbNode, node.name) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.type); }, [SyntaxKind.ObjectBindingPattern]: forEachChildInObjectOrArrayBindingPattern, [SyntaxKind.ArrayBindingPattern]: forEachChildInObjectOrArrayBindingPattern, [SyntaxKind.ArrayLiteralExpression]: function forEachChildInArrayLiteralExpression(node: ArrayLiteralExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); }, [SyntaxKind.ObjectLiteralExpression]: function forEachChildInObjectLiteralExpression(node: ObjectLiteralExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.properties); }, [SyntaxKind.PropertyAccessExpression]: function forEachChildInPropertyAccessExpression(node: PropertyAccessExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.questionDotToken) || visitNode(cbNode, node.name); }, [SyntaxKind.ElementAccessExpression]: function forEachChildInElementAccessExpression(node: ElementAccessExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.questionDotToken) || visitNode(cbNode, node.argumentExpression); }, [SyntaxKind.CallExpression]: forEachChildInCallOrNewExpression, [SyntaxKind.NewExpression]: forEachChildInCallOrNewExpression, [SyntaxKind.TaggedTemplateExpression]: function forEachChildInTaggedTemplateExpression(node: TaggedTemplateExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tag) || visitNode(cbNode, node.questionDotToken) || visitNodes(cbNode, cbNodes, node.typeArguments) || visitNode(cbNode, node.template); }, [SyntaxKind.TypeAssertionExpression]: function forEachChildInTypeAssertionExpression(node: TypeAssertion, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.type) || visitNode(cbNode, node.expression); }, [SyntaxKind.ParenthesizedExpression]: function forEachChildInParenthesizedExpression(node: ParenthesizedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.DeleteExpression]: function forEachChildInDeleteExpression(node: DeleteExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.TypeOfExpression]: function forEachChildInTypeOfExpression(node: TypeOfExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.VoidExpression]: function forEachChildInVoidExpression(node: VoidExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.PrefixUnaryExpression]: function forEachChildInPrefixUnaryExpression(node: PrefixUnaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.operand); }, [SyntaxKind.YieldExpression]: function forEachChildInYieldExpression(node: YieldExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.asteriskToken) || visitNode(cbNode, node.expression); }, [SyntaxKind.AwaitExpression]: function forEachChildInAwaitExpression(node: AwaitExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.PostfixUnaryExpression]: function forEachChildInPostfixUnaryExpression(node: PostfixUnaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.operand); }, [SyntaxKind.BinaryExpression]: function forEachChildInBinaryExpression(node: BinaryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.left) || visitNode(cbNode, node.operatorToken) || visitNode(cbNode, node.right); }, [SyntaxKind.AsExpression]: function forEachChildInAsExpression(node: AsExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.type); }, [SyntaxKind.NonNullExpression]: function forEachChildInNonNullExpression(node: NonNullExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.SatisfiesExpression]: function forEachChildInSatisfiesExpression(node: SatisfiesExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.type); }, [SyntaxKind.MetaProperty]: function forEachChildInMetaProperty(node: MetaProperty, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name); }, [SyntaxKind.ConditionalExpression]: function forEachChildInConditionalExpression(node: ConditionalExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.condition) || visitNode(cbNode, node.questionToken) || visitNode(cbNode, node.whenTrue) || visitNode(cbNode, node.colonToken) || visitNode(cbNode, node.whenFalse); }, [SyntaxKind.SpreadElement]: function forEachChildInSpreadElement(node: SpreadElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.Block]: forEachChildInBlock, [SyntaxKind.ModuleBlock]: forEachChildInBlock, [SyntaxKind.SourceFile]: function forEachChildInSourceFile(node: SourceFile, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.statements) || visitNode(cbNode, node.endOfFileToken); }, [SyntaxKind.VariableStatement]: function forEachChildInVariableStatement(node: VariableStatement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.declarationList); }, [SyntaxKind.VariableDeclarationList]: function forEachChildInVariableDeclarationList(node: VariableDeclarationList, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.declarations); }, [SyntaxKind.ExpressionStatement]: function forEachChildInExpressionStatement(node: ExpressionStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.IfStatement]: function forEachChildInIfStatement(node: IfStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.thenStatement) || visitNode(cbNode, node.elseStatement); }, [SyntaxKind.DoStatement]: function forEachChildInDoStatement(node: DoStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.statement) || visitNode(cbNode, node.expression); }, [SyntaxKind.WhileStatement]: function forEachChildInWhileStatement(node: WhileStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.statement); }, [SyntaxKind.ForStatement]: function forEachChildInForStatement(node: ForStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.initializer) || visitNode(cbNode, node.condition) || visitNode(cbNode, node.incrementor) || visitNode(cbNode, node.statement); }, [SyntaxKind.ForInStatement]: function forEachChildInForInStatement(node: ForInStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.initializer) || visitNode(cbNode, node.expression) || visitNode(cbNode, node.statement); }, [SyntaxKind.ForOfStatement]: function forEachChildInForOfStatement(node: ForOfStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.awaitModifier) || visitNode(cbNode, node.initializer) || visitNode(cbNode, node.expression) || visitNode(cbNode, node.statement); }, [SyntaxKind.ContinueStatement]: forEachChildInContinueOrBreakStatement, [SyntaxKind.BreakStatement]: forEachChildInContinueOrBreakStatement, [SyntaxKind.ReturnStatement]: function forEachChildInReturnStatement(node: ReturnStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.WithStatement]: function forEachChildInWithStatement(node: WithStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.statement); }, [SyntaxKind.SwitchStatement]: function forEachChildInSwitchStatement(node: SwitchStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.caseBlock); }, [SyntaxKind.CaseBlock]: function forEachChildInCaseBlock(node: CaseBlock, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.clauses); }, [SyntaxKind.CaseClause]: function forEachChildInCaseClause(node: CaseClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNodes(cbNode, cbNodes, node.statements); }, [SyntaxKind.DefaultClause]: function forEachChildInDefaultClause(node: DefaultClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.statements); }, [SyntaxKind.LabeledStatement]: function forEachChildInLabeledStatement(node: LabeledStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.label) || visitNode(cbNode, node.statement); }, [SyntaxKind.ThrowStatement]: function forEachChildInThrowStatement(node: ThrowStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.TryStatement]: function forEachChildInTryStatement(node: TryStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tryBlock) || visitNode(cbNode, node.catchClause) || visitNode(cbNode, node.finallyBlock); }, [SyntaxKind.CatchClause]: function forEachChildInCatchClause(node: CatchClause, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.variableDeclaration) || visitNode(cbNode, node.block); }, [SyntaxKind.Decorator]: function forEachChildInDecorator(node: Decorator, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.ClassDeclaration]: forEachChildInClassDeclarationOrExpression, [SyntaxKind.ClassExpression]: forEachChildInClassDeclarationOrExpression, [SyntaxKind.StructDeclaration]: forEachChildInClassDeclarationOrExpression, [SyntaxKind.AnnotationDeclaration]: forEachChildInAnnotationDeclaration, [SyntaxKind.InterfaceDeclaration]: function forEachChildInInterfaceDeclaration(node: InterfaceDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.heritageClauses) || visitNodes(cbNode, cbNodes, node.members); }, [SyntaxKind.TypeAliasDeclaration]: function forEachChildInTypeAliasDeclaration(node: TypeAliasDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNode(cbNode, node.type); }, [SyntaxKind.EnumDeclaration]: function forEachChildInEnumDeclaration(node: EnumDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.members); }, [SyntaxKind.EnumMember]: function forEachChildInEnumMember(node: EnumMember, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name) || visitNode(cbNode, node.initializer); }, [SyntaxKind.ModuleDeclaration]: function forEachChildInModuleDeclaration(node: ModuleDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.body); }, [SyntaxKind.ImportEqualsDeclaration]: function forEachChildInImportEqualsDeclaration(node: ImportEqualsDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNode(cbNode, node.moduleReference); }, [SyntaxKind.ImportDeclaration]: function forEachChildInImportDeclaration(node: ImportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.importClause) || visitNode(cbNode, node.moduleSpecifier) || visitNode(cbNode, node.assertClause); }, [SyntaxKind.ImportClause]: function forEachChildInImportClause(node: ImportClause, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name) || visitNode(cbNode, node.namedBindings); }, [SyntaxKind.AssertClause]: function forEachChildInAssertClause(node: AssertClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); }, [SyntaxKind.AssertEntry]: function forEachChildInAssertEntry(node: AssertEntry, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name) || visitNode(cbNode, node.value); }, [SyntaxKind.NamespaceExportDeclaration]: function forEachChildInNamespaceExportDeclaration(node: NamespaceExportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNode(cbNode, node.name); }, [SyntaxKind.NamespaceImport]: function forEachChildInNamespaceImport(node: NamespaceImport, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name); }, [SyntaxKind.NamespaceExport]: function forEachChildInNamespaceExport(node: NamespaceExport, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name); }, [SyntaxKind.NamedImports]: forEachChildInNamedImportsOrExports, [SyntaxKind.NamedExports]: forEachChildInNamedImportsOrExports, [SyntaxKind.ExportDeclaration]: function forEachChildInExportDeclaration(node: ExportDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.exportClause) || visitNode(cbNode, node.moduleSpecifier) || visitNode(cbNode, node.assertClause); }, [SyntaxKind.ImportSpecifier]: forEachChildInImportOrExportSpecifier, [SyntaxKind.ExportSpecifier]: forEachChildInImportOrExportSpecifier, [SyntaxKind.ExportAssignment]: function forEachChildInExportAssignment(node: ExportAssignment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.expression); }, [SyntaxKind.TemplateExpression]: function forEachChildInTemplateExpression(node: TemplateExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.head) || visitNodes(cbNode, cbNodes, node.templateSpans); }, [SyntaxKind.TemplateSpan]: function forEachChildInTemplateSpan(node: TemplateSpan, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNode(cbNode, node.literal); }, [SyntaxKind.TemplateLiteralType]: function forEachChildInTemplateLiteralType(node: TemplateLiteralTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.head) || visitNodes(cbNode, cbNodes, node.templateSpans); }, [SyntaxKind.TemplateLiteralTypeSpan]: function forEachChildInTemplateLiteralTypeSpan(node: TemplateLiteralTypeSpan, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.type) || visitNode(cbNode, node.literal); }, [SyntaxKind.ComputedPropertyName]: function forEachChildInComputedPropertyName(node: ComputedPropertyName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.HeritageClause]: function forEachChildInHeritageClause(node: HeritageClause, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.types); }, [SyntaxKind.ExpressionWithTypeArguments]: function forEachChildInExpressionWithTypeArguments(node: ExpressionWithTypeArguments, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || visitNodes(cbNode, cbNodes, node.typeArguments); }, [SyntaxKind.ExternalModuleReference]: function forEachChildInExternalModuleReference(node: ExternalModuleReference, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.MissingDeclaration]: function forEachChildInMissingDeclaration(node: MissingDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.illegalDecorators) || visitNodes(cbNode, cbNodes, node.modifiers); }, [SyntaxKind.CommaListExpression]: function forEachChildInCommaListExpression(node: CommaListExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); }, [SyntaxKind.JsxElement]: function forEachChildInJsxElement(node: JsxElement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.openingElement) || visitNodes(cbNode, cbNodes, node.children) || visitNode(cbNode, node.closingElement); }, [SyntaxKind.JsxFragment]: function forEachChildInJsxFragment(node: JsxFragment, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.openingFragment) || visitNodes(cbNode, cbNodes, node.children) || visitNode(cbNode, node.closingFragment); }, [SyntaxKind.JsxSelfClosingElement]: forEachChildInJsxOpeningOrSelfClosingElement, [SyntaxKind.JsxOpeningElement]: forEachChildInJsxOpeningOrSelfClosingElement, [SyntaxKind.JsxAttributes]: function forEachChildInJsxAttributes(node: JsxAttributes, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.properties); }, [SyntaxKind.JsxAttribute]: function forEachChildInJsxAttribute(node: JsxAttribute, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name) || visitNode(cbNode, node.initializer); }, [SyntaxKind.JsxSpreadAttribute]: function forEachChildInJsxSpreadAttribute(node: JsxSpreadAttribute, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, [SyntaxKind.JsxExpression]: function forEachChildInJsxExpression(node: JsxExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.dotDotDotToken) || visitNode(cbNode, node.expression); }, [SyntaxKind.JsxClosingElement]: function forEachChildInJsxClosingElement(node: JsxClosingElement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName); }, [SyntaxKind.OptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.RestType]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.JSDocTypeExpression]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.JSDocNonNullableType]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.JSDocNullableType]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.JSDocOptionalType]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.JSDocVariadicType]: forEachChildInOptionalRestOrJSDocParameterModifier, [SyntaxKind.JSDocFunctionType]: function forEachChildInJSDocFunctionType(node: JSDocFunctionType, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type); }, [SyntaxKind.JSDoc]: function forEachChildInJSDoc(node: JSDoc, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) || visitNodes(cbNode, cbNodes, node.tags); }, [SyntaxKind.JSDocSeeTag]: function forEachChildInJSDocSeeTag(node: JSDocSeeTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.name) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, [SyntaxKind.JSDocNameReference]: function forEachChildInJSDocNameReference(node: JSDocNameReference, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name); }, [SyntaxKind.JSDocMemberName]: function forEachChildInJSDocMemberName(node: JSDocMemberName, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.left) || visitNode(cbNode, node.right); }, [SyntaxKind.JSDocParameterTag]: forEachChildInJSDocParameterOrPropertyTag, [SyntaxKind.JSDocPropertyTag]: forEachChildInJSDocParameterOrPropertyTag, [SyntaxKind.JSDocAuthorTag]: function forEachChildInJSDocAuthorTag(node: JSDocAuthorTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, [SyntaxKind.JSDocImplementsTag]: function forEachChildInJSDocImplementsTag(node: JSDocImplementsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.class) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, [SyntaxKind.JSDocAugmentsTag]: function forEachChildInJSDocAugmentsTag(node: JSDocAugmentsTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.class) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, [SyntaxKind.JSDocTemplateTag]: function forEachChildInJSDocTemplateTag(node: JSDocTemplateTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.constraint) || visitNodes(cbNode, cbNodes, node.typeParameters) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, [SyntaxKind.JSDocTypedefTag]: function forEachChildInJSDocTypedefTag(node: JSDocTypedefTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || (node.typeExpression && node.typeExpression.kind === SyntaxKind.JSDocTypeExpression ? visitNode(cbNode, node.typeExpression) || visitNode(cbNode, node.fullName) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) : visitNode(cbNode, node.fullName) || visitNode(cbNode, node.typeExpression) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment))); }, [SyntaxKind.JSDocCallbackTag]: function forEachChildInJSDocCallbackTag(node: JSDocCallbackTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.fullName) || visitNode(cbNode, node.typeExpression) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); }, [SyntaxKind.JSDocReturnTag]: forEachChildInJSDocReturnTag, [SyntaxKind.JSDocTypeTag]: forEachChildInJSDocReturnTag, [SyntaxKind.JSDocThisTag]: forEachChildInJSDocReturnTag, [SyntaxKind.JSDocEnumTag]: forEachChildInJSDocReturnTag, [SyntaxKind.JSDocSignature]: function forEachChildInJSDocSignature(node: JSDocSignature, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return forEach(node.typeParameters, cbNode) || forEach(node.parameters, cbNode) || visitNode(cbNode, node.type); }, [SyntaxKind.JSDocLink]: forEachChildInJSDocLinkCodeOrPlain, [SyntaxKind.JSDocLinkCode]: forEachChildInJSDocLinkCodeOrPlain, [SyntaxKind.JSDocLinkPlain]: forEachChildInJSDocLinkCodeOrPlain, [SyntaxKind.JSDocTypeLiteral]: function forEachChildInJSDocTypeLiteral(node: JSDocTypeLiteral, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return forEach(node.jsDocPropertyTags, cbNode); }, [SyntaxKind.JSDocTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocClassTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocPublicTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocPrivateTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocProtectedTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocReadonlyTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocDeprecatedTag]: forEachChildInJSDocTag, [SyntaxKind.JSDocOverrideTag]: forEachChildInJSDocTag, [SyntaxKind.PartiallyEmittedExpression]: forEachChildInPartiallyEmittedExpression, }; // shared function forEachChildInCallOrConstructSignature(node: CallSignatureDeclaration | ConstructSignatureDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.parameters) || visitNode(cbNode, node.type); } function forEachChildInUnionOrIntersectionType(node: UnionTypeNode | IntersectionTypeNode, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.types); } function forEachChildInParenthesizedTypeOrTypeOperator(node: ParenthesizedTypeNode | TypeOperatorNode, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.type); } function forEachChildInObjectOrArrayBindingPattern(node: BindingPattern, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); } function forEachChildInCallOrNewExpression(node: CallExpression | NewExpression, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression) || // TODO: should we separate these branches out? visitNode(cbNode, (node as CallExpression).questionDotToken) || visitNodes(cbNode, cbNodes, node.typeArguments) || visitNodes(cbNode, cbNodes, node.arguments); } function forEachChildInBlock(node: Block | ModuleBlock, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.statements); } function forEachChildInContinueOrBreakStatement(node: ContinueStatement | BreakStatement, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.label); } function forEachChildInClassDeclarationOrExpression(node: ClassDeclaration | ClassExpression | StructDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.typeParameters) || visitNodes(cbNode, cbNodes, node.heritageClauses) || visitNodes(cbNode, cbNodes, node.members); } function forEachChildInAnnotationDeclaration(node: AnnotationDeclaration, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.modifiers) || visitNode(cbNode, node.name) || visitNodes(cbNode, cbNodes, node.members); } function forEachChildInNamedImportsOrExports(node: NamedImports | NamedExports, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNodes(cbNode, cbNodes, node.elements); } function forEachChildInImportOrExportSpecifier(node: ImportSpecifier | ExportSpecifier, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.propertyName) || visitNode(cbNode, node.name); } function forEachChildInJsxOpeningOrSelfClosingElement(node: JsxOpeningLikeElement, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNodes(cbNode, cbNodes, node.typeArguments) || visitNode(cbNode, node.attributes); } function forEachChildInOptionalRestOrJSDocParameterModifier(node: OptionalTypeNode | RestTypeNode | JSDocTypeExpression | JSDocNullableType | JSDocNonNullableType | JSDocOptionalType | JSDocVariadicType, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.type); } function forEachChildInJSDocParameterOrPropertyTag(node: JSDocParameterTag | JSDocPropertyTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || (node.isNameFirst ? visitNode(cbNode, node.name) || visitNode(cbNode, node.typeExpression) : visitNode(cbNode, node.typeExpression) || visitNode(cbNode, node.name)) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); } function forEachChildInJSDocReturnTag(node: JSDocReturnTag | JSDocTypeTag | JSDocThisTag | JSDocEnumTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || visitNode(cbNode, node.typeExpression) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); } function forEachChildInJSDocLinkCodeOrPlain(node: JSDocLink | JSDocLinkCode | JSDocLinkPlain, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.name); } function forEachChildInJSDocTag(node: JSDocUnknownTag | JSDocClassTag | JSDocPublicTag | JSDocPrivateTag | JSDocProtectedTag | JSDocReadonlyTag | JSDocDeprecatedTag | JSDocOverrideTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)); } function forEachChildInPartiallyEmittedExpression(node: PartiallyEmittedExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); } /** * 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. * * @param node a given node to visit its children * @param cbNode a callback to be invoked for all child nodes * @param cbNodes a callback to be invoked for embedded array * * @remarks `forEachChild` must visit the children of a node in the order * that they appear in the source code. The language service depends on this property to locate nodes by position. */ export function forEachChild(node: Node, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { if (node === undefined || node.kind <= SyntaxKind.LastToken) { return; } const fn = (forEachChildTable as Record>)[node.kind]; return fn === undefined ? undefined : fn(node, cbNode, cbNodes); } /** * 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; additionally, * unlike `forEachChild`, 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. * * @param node a given node to visit its children * @param cbNode a callback to be invoked for all child nodes * @param cbNodes a callback to be invoked for embedded array * * @remarks Unlike `forEachChild`, `forEachChildRecursively` handles recursively invoking the traversal on each child node found, * and while doing so, handles traversing the structure without relying on the callstack to encode the tree structure. * @internal */ export function forEachChildRecursively(rootNode: Node, cbNode: (node: Node, parent: Node) => T | "skip" | undefined, cbNodes?: (nodes: NodeArray, parent: Node) => T | "skip" | undefined): T | undefined { const queue: (Node | NodeArray)[] = gatherPossibleChildren(rootNode); const parents: Node[] = []; // tracks parent references for elements in queue while (parents.length < queue.length) { parents.push(rootNode); } while (queue.length !== 0) { const current = queue.pop()!; const parent = parents.pop()!; if (isArray(current)) { if (cbNodes) { const res = cbNodes(current, parent); if (res) { if (res === "skip") continue; return res; } } for (let i = current.length - 1; i >= 0; --i) { queue.push(current[i]); parents.push(parent); } } else { const res = cbNode(current, parent); if (res) { if (res === "skip") continue; return res; } if (current.kind >= SyntaxKind.FirstNode) { // add children in reverse order to the queue, so popping gives the first child for (const child of gatherPossibleChildren(current)) { queue.push(child); parents.push(current); } } } } } function gatherPossibleChildren(node: Node) { const children: (Node | NodeArray)[] = []; forEachChild(node, addWorkItem, addWorkItem); // By using a stack above and `unshift` here, we emulate a depth-first preorder traversal return children; function addWorkItem(n: Node | NodeArray) { children.unshift(n); } } export interface CreateSourceFileOptions { languageVersion: ScriptTarget; /** * Controls the format the file is detected as - this can be derived from only the path * and files on disk, but needs to be done with a module resolution cache in scope to be performant. * This is usually `undefined` for compilations that do not have `moduleResolution` values of `node16` or `nodenext`. */ impliedNodeFormat?: ModuleKind.ESNext | ModuleKind.CommonJS; /** * Controls how module-y-ness is set for the given file. Usually the result of calling * `getSetExternalModuleIndicator` on a valid `CompilerOptions` object. If not present, the default * check specified by `isFileProbablyExternalModule` will be used to set the field. */ setExternalModuleIndicator?: (file: SourceFile) => void; /** @internal */ packageJsonLocations?: readonly string[]; /** @internal */ packageJsonScope?: PackageJsonInfo; } function setExternalModuleIndicator(sourceFile: SourceFile) { sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile); } let sourceFileCompilerOptions: CompilerOptions; let isArkguardInputSourceFile: boolean = false; export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind, options?: CompilerOptions, isArkguardInput?: boolean): SourceFile { tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true); const recordInfo = MemoryDotting.recordStage(MemoryDotting.CREATE_SORUCE_FILE_PARSE); performance.mark("beforeParse"); let result: SourceFile; sourceFileCompilerOptions = options ?? defaultInitCompilerOptions; isArkguardInputSourceFile = isArkguardInput ?? false; perfLogger.logStartParseSourceFile(fileName); const { languageVersion, setExternalModuleIndicator: overrideSetExternalModuleIndicator, impliedNodeFormat: format } = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : ({ languageVersion: languageVersionOrOptions } as CreateSourceFileOptions); if (languageVersion === ScriptTarget.JSON) { result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON, noop); } else { const setIndicator = format === undefined ? overrideSetExternalModuleIndicator : (file: SourceFile) => { file.impliedNodeFormat = format; return (overrideSetExternalModuleIndicator || setExternalModuleIndicator)(file); }; result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind, setIndicator); } perfLogger.logStopParseSourceFile(); performance.mark("afterParse"); MemoryDotting.stopRecordStage(recordInfo); performance.measure("Parse", "beforeParse", "afterParse"); tracing?.pop(); return result; } export function parseIsolatedEntityName(text: string, languageVersion: ScriptTarget): EntityName | undefined { return Parser.parseIsolatedEntityName(text, languageVersion); } /** * Parse json text into SyntaxTree and return node and parse errors if any * @param fileName * @param sourceText */ export function parseJsonText(fileName: string, sourceText: string): JsonSourceFile { return Parser.parseJsonText(fileName, sourceText); } // See also `isExternalOrCommonJsModule` in utilities.ts export function isExternalModule(file: SourceFile): boolean { return file.externalModuleIndicator !== undefined; } // 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 = false, option?: CompilerOptions): SourceFile { sourceFileCompilerOptions = option ?? defaultInitCompilerOptions; const newSourceFile = IncrementalParser.updateSourceFile(sourceFile, newText, textChangeRange, aggressiveChecks); // Because new source file node is created, it may not have the flag PossiblyContainDynamicImport. This is the case if there is no new edit to add dynamic import. // We will manually port the flag to the new source file. (newSourceFile as Mutable).flags |= (sourceFile.flags & NodeFlags.PermanentlySetIncrementalFlags); return newSourceFile; } /** @internal */ export function parseIsolatedJSDocComment(content: string, start?: number, length?: number) { const result = Parser.JSDocParser.parseIsolatedJSDocComment(content, start, length); if (result && result.jsDoc) { // because the jsDocComment was parsed out of the source file, it might // not be covered by the fixupParentReferences. Parser.fixupParentReferences(result.jsDoc); } return result; } /** @internal */ // Exposed only for testing. export function parseJSDocTypeExpressionForTests(content: string, start?: number, length?: number) { return Parser.JSDocParser.parseJSDocTypeExpressionForTests(content, start, length); } // Set language version: ArkTS 1.1、1.2 let languageVersionCallBack: ((filePath: string) => boolean) | undefined; export function setLanguageVersionByFilePath(getLanguageVersion: ((filePath: string) => boolean) | undefined): void { languageVersionCallBack = getLanguageVersion; } // 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. namespace Parser { /* eslint-disable no-var */ // 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. var scanner = createScanner(ScriptTarget.Latest, /*skipTrivia*/ true); var disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext; // capture constructors in 'initializeState' to avoid null checks var NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; var TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; var IdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; var PrivateIdentifierConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; var SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; function countNode(node: Node) { nodeCount++; return node; } // Rather than using `createBaseNodeFactory` here, we establish a `BaseNodeFactory` that closes over the // constructors above, which are reset each time `initializeState` is called. var baseNodeFactory: BaseNodeFactory = { createBaseSourceFileNode: kind => countNode(new SourceFileConstructor(kind, /*pos*/ 0, /*end*/ 0)), createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)), createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)), createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)) }; var factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory); var fileName: string; var sourceFlags: NodeFlags; var sourceText: string; var languageVersion: ScriptTarget; var scriptKind: ScriptKind; var languageVariant: LanguageVariant; var parseDiagnostics: DiagnosticWithDetachedLocation[]; var jsDocDiagnostics: DiagnosticWithDetachedLocation[]; var syntaxCursor: IncrementalParser.SyntaxCursor | undefined; var currentToken: SyntaxKind; var nodeCount: number; var identifiers: ESMap; var privateIdentifiers: ESMap; var identifierCount: number; var parsingContext: ParsingContext; var notParenthesizedArrow: Set | undefined; // 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. var contextFlags: NodeFlags; var etsFlags: EtsFlags; // Indicates whether we are currently parsing top-level statements. var topLevel = true; // 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. var parseErrorBeforeNextFinishedNode = false; var extendEtsComponentDeclaration: { name: string, type: string, instance: string } | undefined; var stylesEtsComponentDeclaration: { name: string, type: string, instance: string } | undefined; // A map to record file scope '@styles' function name var fileStylesComponents = new Map(); var currentStructName: string | undefined; var stateStylesRootNode: string | undefined; // A map to record struct scope '@styles' method name var structStylesComponents = new Map(); /* eslint-enable no-var */ export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind, setExternalModuleIndicatorOverride?: (file: SourceFile) => void): SourceFile { scriptKind = ensureScriptKind(fileName, scriptKind); if (scriptKind === ScriptKind.JSON) { const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes); convertToObjectWorker(result, result.statements[0]?.expression, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined); result.referencedFiles = emptyArray; result.typeReferenceDirectives = emptyArray; result.libReferenceDirectives = emptyArray; result.amdDependencies = emptyArray; result.hasNoDefaultLib = false; result.pragmas = emptyMap as ReadonlyPragmaMap; return result; } initializeState(fileName, sourceText, languageVersion, syntaxCursor, scriptKind); const result = parseSourceFileWorker(languageVersion, setParentNodes, scriptKind, setExternalModuleIndicatorOverride || setExternalModuleIndicator); clearState(); return result; } export function parseIsolatedEntityName(content: string, languageVersion: ScriptTarget): EntityName | undefined { // Choice of `isDeclarationFile` should be arbitrary initializeState("", content, languageVersion, /*syntaxCursor*/ undefined, ScriptKind.JS); // Prime the scanner. nextToken(); const entityName = parseEntityName(/*allowReservedWords*/ true); const isInvalid = token() === SyntaxKind.EndOfFileToken && !parseDiagnostics.length; clearState(); return isInvalid ? entityName : undefined; } export function parseJsonText(fileName: string, sourceText: string, languageVersion: ScriptTarget = ScriptTarget.ES2015, syntaxCursor?: IncrementalParser.SyntaxCursor, setParentNodes = false): JsonSourceFile { initializeState(fileName, sourceText, languageVersion, syntaxCursor, ScriptKind.JSON); sourceFlags = contextFlags; // Prime the scanner. nextToken(); const pos = getNodePos(); let statements, endOfFileToken; if (token() === SyntaxKind.EndOfFileToken) { statements = createNodeArray([], pos, pos); endOfFileToken = parseTokenNode(); } else { // Loop and synthesize an ArrayLiteralExpression if there are more than // one top-level expressions to ensure all input text is consumed. let expressions: Expression[] | Expression | undefined; while (token() !== SyntaxKind.EndOfFileToken) { let expression; switch (token()) { case SyntaxKind.OpenBracketToken: expression = parseArrayLiteralExpression(); break; case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: expression = parseTokenNode(); break; case SyntaxKind.MinusToken: if (lookAhead(() => nextToken() === SyntaxKind.NumericLiteral && nextToken() !== SyntaxKind.ColonToken)) { expression = parsePrefixUnaryExpression() as JsonMinusNumericLiteral; } else { expression = parseObjectLiteralExpression(); } break; case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: if (lookAhead(() => nextToken() !== SyntaxKind.ColonToken)) { expression = parseLiteralNode() as StringLiteral | NumericLiteral; break; } // falls through default: expression = parseObjectLiteralExpression(); break; } // Error recovery: collect multiple top-level expressions if (expressions && isArray(expressions)) { expressions.push(expression); } else if (expressions) { expressions = [expressions, expression]; } else { expressions = expression; if (token() !== SyntaxKind.EndOfFileToken) { parseErrorAtCurrentToken(Diagnostics.Unexpected_token); } } } const expression = isArray(expressions) ? finishNode(factory.createArrayLiteralExpression(expressions), pos) : Debug.checkDefined(expressions); const statement = factory.createExpressionStatement(expression) as JsonObjectExpressionStatement; finishNode(statement, pos); statements = createNodeArray([statement], pos); endOfFileToken = parseExpectedToken(SyntaxKind.EndOfFileToken, Diagnostics.Unexpected_token); } // Set source file so that errors will be reported with this file name const sourceFile = createSourceFile(fileName, ScriptTarget.ES2015, ScriptKind.JSON, /*isDeclaration*/ false, statements, endOfFileToken, sourceFlags, noop); if (setParentNodes) { fixupParentReferences(sourceFile); } sourceFile.nodeCount = nodeCount; sourceFile.identifierCount = identifierCount; sourceFile.identifiers = identifiers; sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); if (jsDocDiagnostics) { sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } const result = sourceFile as JsonSourceFile; clearState(); return result; } function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) { NodeConstructor = objectAllocator.getNodeConstructor(); TokenConstructor = objectAllocator.getTokenConstructor(); IdentifierConstructor = objectAllocator.getIdentifierConstructor(); PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor(); SourceFileConstructor = objectAllocator.getSourceFileConstructor(); fileName = normalizePath(_fileName); sourceText = _sourceText; languageVersion = _languageVersion; syntaxCursor = _syntaxCursor; scriptKind = _scriptKind; languageVariant = getLanguageVariant(_scriptKind); parseDiagnostics = []; parsingContext = 0; identifiers = new Map(); privateIdentifiers = new Map(); identifierCount = 0; nodeCount = 0; sourceFlags = 0; topLevel = true; switch (scriptKind) { case ScriptKind.JS: case ScriptKind.JSX: contextFlags = NodeFlags.JavaScriptFile; break; case ScriptKind.JSON: contextFlags = NodeFlags.JavaScriptFile | NodeFlags.JsonFile; break; case ScriptKind.ETS: contextFlags = NodeFlags.EtsContext; break; default: contextFlags = NodeFlags.None; break; } if (fileName.endsWith(Extension.Ets)) { contextFlags = NodeFlags.EtsContext; } parseErrorBeforeNextFinishedNode = false; // Initialize and prime the scanner before parsing the source elements. scanner.setText(sourceText); scanner.setOnError(scanError); scanner.setScriptTarget(languageVersion); scanner.setLanguageVariant(languageVariant); scanner.setEtsContext(inEtsContext()); } function clearState() { // Clear out the text the scanner is pointing at, so it doesn't keep anything alive unnecessarily. scanner.clearCommentDirectives(); scanner.setText(""); scanner.setOnError(undefined); scanner.setEtsContext(false); // Clear any data. We don't want to accidentally hold onto it for too long. sourceText = undefined!; languageVersion = undefined!; syntaxCursor = undefined; scriptKind = undefined!; languageVariant = undefined!; sourceFlags = 0; parseDiagnostics = undefined!; jsDocDiagnostics = undefined!; parsingContext = 0; identifiers = undefined!; notParenthesizedArrow = undefined; topLevel = true; extendEtsComponentDeclaration = undefined; stylesEtsComponentDeclaration = undefined; stateStylesRootNode = undefined; fileStylesComponents.clear(); structStylesComponents.clear(); } function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void): SourceFile { const isDeclarationFile = isDeclarationFileName(fileName); if (isDeclarationFile) { contextFlags |= NodeFlags.Ambient; } sourceFlags = contextFlags; // Prime the scanner. nextToken(); let statements = parseList(ParsingContext.SourceElements, parseStatement); const markedkitImportRanges = new Array(); const sdkPath = getSdkPath(sourceFileCompilerOptions); // When the language version is 1.2, skip processKit statements = (!!sourceFileCompilerOptions.noTransformedKitInParser || !sdkPath || parseDiagnostics.length || languageVersionCallBack?.(fileName)) ? statements : createNodeArray(processKit(factory, statements, sdkPath, markedkitImportRanges, inEtsContext(), sourceFileCompilerOptions), statements.pos); Debug.assert(token() === SyntaxKind.EndOfFileToken); const endOfFileToken = addJSDocComment(parseTokenNode()); const sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile, statements, endOfFileToken, sourceFlags, setExternalModuleIndicator); if (markedkitImportRanges.length > 0) { sourceFile.markedKitImportRange = markedkitImportRanges; } // A member of ReadonlyArray isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future processCommentPragmas(sourceFile as {} as PragmaContext, sourceText); processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic); sourceFile.commentDirectives = scanner.getCommentDirectives(); sourceFile.nodeCount = nodeCount; sourceFile.identifierCount = identifierCount; sourceFile.identifiers = identifiers; sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); if (jsDocDiagnostics) { sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } if (setParentNodes) { fixupParentReferences(sourceFile); } return sourceFile; function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) { parseDiagnostics.push(createDetachedDiagnostic(fileName, pos, end, diagnostic)); } } function withJSDoc(node: T, hasJSDoc: boolean): T { return hasJSDoc ? addJSDocComment(node) : node; } let hasDeprecatedTag = false; function addJSDocComment(node: T): T { Debug.assert(!node.jsDoc); // Should only be called once per node const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos)); if (jsDoc.length) node.jsDoc = jsDoc; if (hasDeprecatedTag) { hasDeprecatedTag = false; (node as Mutable).flags |= NodeFlags.Deprecated; } return node; } function reparseTopLevelAwait(sourceFile: SourceFile) { const savedSyntaxCursor = syntaxCursor; const baseSyntaxCursor = IncrementalParser.createSyntaxCursor(sourceFile); syntaxCursor = { currentNode }; const statements: Statement[] = []; const savedParseDiagnostics = parseDiagnostics; parseDiagnostics = []; let pos = 0; let start = findNextStatementWithAwait(sourceFile.statements, 0); while (start !== -1) { // append all statements between pos and start const prevStatement = sourceFile.statements[pos]; const nextStatement = sourceFile.statements[start]; addRange(statements, sourceFile.statements, pos, start); pos = findNextStatementWithoutAwait(sourceFile.statements, start); // append all diagnostics associated with the copied range const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); const diagnosticEnd = diagnosticStart >= 0 ? findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= nextStatement.pos, diagnosticStart) : -1; if (diagnosticStart >= 0) { addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart, diagnosticEnd >= 0 ? diagnosticEnd : undefined); } // reparse all statements between start and pos. We skip existing diagnostics for the same range and allow the parser to generate new ones. speculationHelper(() => { const savedContextFlags = contextFlags; contextFlags |= NodeFlags.AwaitContext; scanner.setTextPos(nextStatement.pos); nextToken(); while (token() !== SyntaxKind.EndOfFileToken) { const startPos = scanner.getStartPos(); const statement = parseListElement(ParsingContext.SourceElements, parseStatement); statements.push(statement); if (startPos === scanner.getStartPos()) { nextToken(); } if (pos >= 0) { const nonAwaitStatement = sourceFile.statements[pos]; if (statement.end === nonAwaitStatement.pos) { // done reparsing this section break; } if (statement.end > nonAwaitStatement.pos) { // we ate into the next statement, so we must reparse it. pos = findNextStatementWithoutAwait(sourceFile.statements, pos + 1); } } } contextFlags = savedContextFlags; }, SpeculationKind.Reparse); // find the next statement containing an `await` start = pos >= 0 ? findNextStatementWithAwait(sourceFile.statements, pos) : -1; } // append all statements between pos and the end of the list if (pos >= 0) { const prevStatement = sourceFile.statements[pos]; addRange(statements, sourceFile.statements, pos); // append all diagnostics associated with the copied range const diagnosticStart = findIndex(savedParseDiagnostics, diagnostic => diagnostic.start >= prevStatement.pos); if (diagnosticStart >= 0) { addRange(parseDiagnostics, savedParseDiagnostics, diagnosticStart); } } syntaxCursor = savedSyntaxCursor; return factory.updateSourceFile(sourceFile, setTextRange(factory.createNodeArray(statements), sourceFile.statements)); function containsPossibleTopLevelAwait(node: Node) { return !(node.flags & NodeFlags.AwaitContext) && !!(node.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait); } function findNextStatementWithAwait(statements: NodeArray, start: number) { for (let i = start; i < statements.length; i++) { if (containsPossibleTopLevelAwait(statements[i])) { return i; } } return -1; } function findNextStatementWithoutAwait(statements: NodeArray, start: number) { for (let i = start; i < statements.length; i++) { if (!containsPossibleTopLevelAwait(statements[i])) { return i; } } return -1; } function currentNode(position: number) { const node = baseSyntaxCursor.currentNode(position); if (topLevel && node && containsPossibleTopLevelAwait(node)) { node.intersectsChange = true; } return node; } } export function fixupParentReferences(rootNode: 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. setParentRecursive(rootNode, /*incremental*/ true); } function createSourceFile( fileName: string, languageVersion: ScriptTarget, scriptKind: ScriptKind, isDeclarationFile: boolean, statements: readonly Statement[], endOfFileToken: EndOfFileToken, flags: NodeFlags, setExternalModuleIndicator: (sourceFile: SourceFile) => void): SourceFile { // code from createNode is inlined here so createNode won't have to deal with special case of creating source files // this is quite rare comparing to other nodes and createNode should be as fast as possible let sourceFile = factory.createSourceFile(statements, endOfFileToken, flags); setTextRangePosWidth(sourceFile, 0, sourceText.length); setFields(sourceFile); // If we parsed this as an external module, it may contain top-level await if (!isDeclarationFile && isExternalModule(sourceFile) && sourceFile.transformFlags & TransformFlags.ContainsPossibleTopLevelAwait) { sourceFile = reparseTopLevelAwait(sourceFile); setFields(sourceFile); } return sourceFile; function setFields(sourceFile: SourceFile) { sourceFile.text = sourceText; sourceFile.bindDiagnostics = []; sourceFile.bindSuggestionDiagnostics = undefined; sourceFile.languageVersion = languageVersion; sourceFile.fileName = fileName; sourceFile.languageVariant = getLanguageVariant(scriptKind); sourceFile.isDeclarationFile = isDeclarationFile; sourceFile.scriptKind = scriptKind; setExternalModuleIndicator(sourceFile); sourceFile.setExternalModuleIndicator = setExternalModuleIndicator; } } function setContextFlag(val: boolean, flag: NodeFlags) { if (val) { contextFlags |= flag; } else { contextFlags &= ~flag; } } function setEtsFlag(val: boolean, flag: EtsFlags) { if (val) { etsFlags |= flag; } else { etsFlags &= ~flag; } } function setDisallowInContext(val: boolean) { setContextFlag(val, NodeFlags.DisallowInContext); } function setYieldContext(val: boolean) { setContextFlag(val, NodeFlags.YieldContext); } function setDecoratorContext(val: boolean) { setContextFlag(val, NodeFlags.DecoratorContext); } function setAwaitContext(val: boolean) { setContextFlag(val, NodeFlags.AwaitContext); } function setStructContext(val: boolean) { setEtsFlag(val, EtsFlags.StructContext); } function setEtsComponentsContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsComponentsContext); } function setEtsNewExpressionContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsNewExpressionContext); } function setEtsExtendComponentsContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsExtendComponentsContext); } function setEtsStylesComponentsContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsStylesComponentsContext); } function setEtsBuildContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsBuildContext); } function setEtsBuilderContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsBuilderContext); } function setEtsStateStylesContext(val: boolean) { setEtsFlag(val, EtsFlags.EtsStateStylesContext); } function setUICallbackContext(val: boolean) { setEtsFlag(val, EtsFlags.UICallbackContext); } function setSyntaxComponentContext(val: boolean) { setEtsFlag(val, EtsFlags.SyntaxComponentContext); } function setSyntaxDataSourceContext(val: boolean) { setEtsFlag(val, EtsFlags.SyntaxDataSourceContext); } function setNoEtsComponentContext(val: boolean) { setEtsFlag(val, EtsFlags.NoEtsComponentContext); } let firstArgumentExpression: boolean = false; function setFirstArgumentExpression(val: boolean): void { firstArgumentExpression = val; } function getFirstArgumentExpression(): boolean { return firstArgumentExpression; } let repeatEachRest: boolean = false; function setRepeatEachRest(val: boolean): void { repeatEachRest = val; } function getRepeatEachRest(): boolean { return repeatEachRest; } function doOutsideOfContext(context: NodeFlags, func: () => T): T { // contextFlagsToClear will contain only the context flags that are // currently set that we need to temporarily clear // We don't just blindly reset to the previous flags to ensure // that we do not mutate cached flags for the incremental // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and // HasAggregatedChildData). const contextFlagsToClear = context & contextFlags; if (contextFlagsToClear) { // clear the requested context flags setContextFlag(/*val*/ false, contextFlagsToClear); const result = func(); // restore the context flags we just cleared setContextFlag(/*val*/ true, contextFlagsToClear); return result; } // no need to do anything special as we are not in any of the requested contexts return func(); } function doInsideOfContext(context: NodeFlags, func: () => T): T { // contextFlagsToSet will contain only the context flags that // are not currently set that we need to temporarily enable. // We don't just blindly reset to the previous flags to ensure // that we do not mutate cached flags for the incremental // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and // HasAggregatedChildData). const contextFlagsToSet = context & ~contextFlags; if (contextFlagsToSet) { // set the requested context flags setContextFlag(/*val*/ true, contextFlagsToSet); const result = func(); // reset the context flags we just set setContextFlag(/*val*/ false, contextFlagsToSet); return result; } // no need to do anything special as we are already in all of the requested contexts return func(); } function allowInAnd(func: () => T): T { return doOutsideOfContext(NodeFlags.DisallowInContext, func); } function disallowInAnd(func: () => T): T { return doInsideOfContext(NodeFlags.DisallowInContext, func); } function allowConditionalTypesAnd(func: () => T): T { return doOutsideOfContext(NodeFlags.DisallowConditionalTypesContext, func); } function disallowConditionalTypesAnd(func: () => T): T { return doInsideOfContext(NodeFlags.DisallowConditionalTypesContext, func); } function doInYieldContext(func: () => T): T { return doInsideOfContext(NodeFlags.YieldContext, func); } function doInDecoratorContext(func: () => T): T { // setting Ets Extend Components const extendDecorator = sourceFileCompilerOptions.ets?.extend?.decorator ?? "Extend"; if (token() === SyntaxKind.Identifier && scanner.getTokenText() === extendDecorator) { setEtsFlag(true, EtsFlags.EtsExtendComponentsContext); } // setting Ets Styles Components const stylesDecorator = sourceFileCompilerOptions.ets?.styles?.decorator ?? "Styles"; if (token() === SyntaxKind.Identifier && scanner.getTokenText() === stylesDecorator) { setEtsFlag(true, EtsFlags.EtsStylesComponentsContext); } return doInsideOfContext(NodeFlags.DecoratorContext, func); } function doInAwaitContext(func: () => T): T { return doInsideOfContext(NodeFlags.AwaitContext, func); } function doOutsideOfAwaitContext(func: () => T): T { return doOutsideOfContext(NodeFlags.AwaitContext, func); } function doInYieldAndAwaitContext(func: () => T): T { return doInsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); } function doOutsideOfYieldAndAwaitContext(func: () => T): T { return doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext, func); } function inContext(flags: NodeFlags) { return (contextFlags & flags) !== 0; } function inEtsFlagsContext(flags: EtsFlags) { return (etsFlags & flags) !== 0; } function inYieldContext() { return inContext(NodeFlags.YieldContext); } function inDisallowInContext() { return inContext(NodeFlags.DisallowInContext); } function inDisallowConditionalTypesContext() { return inContext(NodeFlags.DisallowConditionalTypesContext); } function inDecoratorContext() { return inContext(NodeFlags.DecoratorContext); } function inAwaitContext() { return inContext(NodeFlags.AwaitContext); } function parseErrorAtCurrentToken(message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { return parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), message, arg0); } function inEtsContext() { return inContext(NodeFlags.EtsContext); } function inStructContext() { return inEtsContext() && inEtsFlagsContext(EtsFlags.StructContext); } function inEtsComponentsContext() { return inEtsContext() && inEtsFlagsContext(EtsFlags.EtsComponentsContext); } function inEtsNewExpressionContext() { return inEtsContext() && inEtsFlagsContext(EtsFlags.EtsNewExpressionContext); } function inEtsExtendComponentsContext() { return inEtsContext() && inEtsFlagsContext(EtsFlags.EtsExtendComponentsContext); } function inEtsStylesComponentsContext() { return inEtsContext() && inEtsFlagsContext(EtsFlags.EtsStylesComponentsContext); } function inBuildContext() { return inEtsContext() && inStructContext() && inEtsFlagsContext(EtsFlags.EtsBuildContext); } function inBuilderContext() { return inEtsContext() && inEtsFlagsContext(EtsFlags.EtsBuilderContext); } function inEtsStateStylesContext() { return inEtsContext() && (inBuildContext() || inBuilderContext() || inEtsExtendComponentsContext() || inEtsStylesComponentsContext()) && inEtsFlagsContext(EtsFlags.EtsStateStylesContext); } function inUICallbackContext() { return inEtsContext() && (inBuildContext() || inBuilderContext()) && inEtsFlagsContext(EtsFlags.UICallbackContext); } function inSyntaxComponentContext() { return inEtsContext() && (inBuildContext() || inBuilderContext()) && inEtsFlagsContext(EtsFlags.SyntaxComponentContext); } function inSyntaxDataSourceContext() { return inEtsContext() && (inBuildContext() || inBuilderContext()) && inEtsFlagsContext(EtsFlags.SyntaxDataSourceContext); } function inNoEtsComponentContext() { return inEtsContext() && (inBuildContext() || inBuilderContext()) && inEtsFlagsContext(EtsFlags.NoEtsComponentContext); } function inAllowAnnotationContext() { return (inEtsContext() || isArkguardInputSourceFile) && sourceFileCompilerOptions?.etsAnnotationsEnable === true; } function parseErrorAtPosition(start: number, length: number, message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { // Don't report another error if it would just be at the same position as the last error. const lastError = lastOrUndefined(parseDiagnostics); let result: DiagnosticWithDetachedLocation | undefined; if (!lastError || start !== lastError.start) { result = createDetachedDiagnostic(fileName, start, length, message, arg0); parseDiagnostics.push(result); } // 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; return result; } function parseErrorAt(start: number, end: number, message: DiagnosticMessage, arg0?: any): DiagnosticWithDetachedLocation | undefined { return parseErrorAtPosition(start, end - start, message, arg0); } function parseErrorAtRange(range: TextRange, message: DiagnosticMessage, arg0?: any): void { parseErrorAt(range.pos, range.end, message, arg0); } function scanError(message: DiagnosticMessage, length: number): void { parseErrorAtPosition(scanner.getTextPos(), length, message); } function getNodePos(): number { return scanner.getStartPos(); } function hasPrecedingJSDocComment() { return scanner.hasPrecedingJSDocComment(); } // Use this function to access the current token instead of reading the currentToken // variable. Since function results aren't narrowed in control flow analysis, this ensures // that the type checker doesn't make wrong assumptions about the type of the current // token (e.g. a call to nextToken() changes the current token but the checker doesn't // reason about this side effect). Mainstream VMs inline simple functions like this, so // there is no performance penalty. function token(): SyntaxKind { return currentToken; } function nextTokenWithoutCheck() { return currentToken = scanner.scan(); } function nextTokenAnd(func: () => T): T { nextToken(); return func(); } function nextToken(): SyntaxKind { // if the keyword had an escape if (isKeyword(currentToken) && (scanner.hasUnicodeEscape() || scanner.hasExtendedUnicodeEscape())) { // issue a parse error for the escape parseErrorAt(scanner.getTokenPos(), scanner.getTextPos(), Diagnostics.Keywords_cannot_contain_escape_characters); } return nextTokenWithoutCheck(); } function nextTokenJSDoc(): JSDocSyntaxKind { return currentToken = scanner.scanJsDocToken(); } function reScanGreaterToken(): SyntaxKind { return currentToken = scanner.reScanGreaterToken(); } function reScanSlashToken(): SyntaxKind { return currentToken = scanner.reScanSlashToken(); } function reScanTemplateToken(isTaggedTemplate: boolean): SyntaxKind { return currentToken = scanner.reScanTemplateToken(isTaggedTemplate); } function reScanTemplateHeadOrNoSubstitutionTemplate(): SyntaxKind { return currentToken = scanner.reScanTemplateHeadOrNoSubstitutionTemplate(); } function reScanLessThanToken(): SyntaxKind { return currentToken = scanner.reScanLessThanToken(); } function reScanHashToken(): SyntaxKind { return currentToken = scanner.reScanHashToken(); } function scanJsxIdentifier(): SyntaxKind { return currentToken = scanner.scanJsxIdentifier(); } function scanJsxText(): SyntaxKind { return currentToken = scanner.scanJsxToken(); } function scanJsxAttributeValue(): SyntaxKind { return currentToken = scanner.scanJsxAttributeValue(); } function speculationHelper(callback: () => T, speculationKind: SpeculationKind): 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). const saveToken = currentToken; const saveParseDiagnosticsLength = parseDiagnostics.length; const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; // Note: it is not actually necessary to save/restore the context flags here. That's // because the saving/restoring 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 invariant holds. const 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. const result = speculationKind !== SpeculationKind.TryParse ? 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 || speculationKind !== SpeculationKind.TryParse) { currentToken = saveToken; if (speculationKind !== SpeculationKind.Reparse) { 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(callback: () => T): T { return speculationHelper(callback, SpeculationKind.Lookahead); } /** 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(callback: () => T): T { return speculationHelper(callback, SpeculationKind.TryParse); } function isBindingIdentifier(): boolean { if (token() === SyntaxKind.Identifier) { return true; } // `let await`/`let yield` in [Yield] or [Await] are allowed here and disallowed in the binder. return token() > SyntaxKind.LastReservedWord; } // 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; } // If we have a 'await' keyword, and we're in the [Await] context, then 'await' is // considered a keyword and is not an identifier. if (token() === SyntaxKind.AwaitKeyword && inAwaitContext()) { return false; } return token() > SyntaxKind.LastReservedWord; } function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean { if (token() === kind) { if (shouldAdvance) { nextToken(); } return true; } if (token() !== kind && stateStylesRootNode && inEtsStateStylesContext()) { 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; } const viableKeywordSuggestions = Object.keys(textToKeywordObj).filter(keyword => keyword.length > 2); /** * Provides a better error message than the generic "';' expected" if possible for * known common variants of a missing semicolon, such as from a mispelled names. * * @param node Node preceding the expected semicolon location. */ function parseErrorForMissingSemicolonAfter(node: Expression | PropertyName): void { // Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.: // module `M1` { // ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`. if (isTaggedTemplateExpression(node)) { parseErrorAt(skipTrivia(sourceText, node.template.pos), node.template.end, Diagnostics.Module_declaration_names_may_only_use_or_quoted_strings); return; } // Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message. const expressionText = ts.isIdentifier(node) ? idText(node) : undefined; if (!expressionText || !isIdentifierText(expressionText, languageVersion)) { parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); return; } const pos = skipTrivia(sourceText, node.pos); // Some known keywords are likely signs of syntax being used improperly. switch (expressionText) { case "const": case "let": case "var": parseErrorAt(pos, node.end, Diagnostics.Variable_declaration_not_allowed_at_this_location); return; case "declare": // If a declared node failed to parse, it would have emitted a diagnostic already. return; case "interface": parseErrorForInvalidName(Diagnostics.Interface_name_cannot_be_0, Diagnostics.Interface_must_be_given_a_name, SyntaxKind.OpenBraceToken); return; case "is": parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods); return; case "module": case "namespace": parseErrorForInvalidName(Diagnostics.Namespace_name_cannot_be_0, Diagnostics.Namespace_must_be_given_a_name, SyntaxKind.OpenBraceToken); return; case "type": parseErrorForInvalidName(Diagnostics.Type_alias_name_cannot_be_0, Diagnostics.Type_alias_must_be_given_a_name, SyntaxKind.EqualsToken); return; } // The user alternatively might have misspelled or forgotten to add a space after a common keyword. const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText); if (suggestion) { parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion); return; } // Unknown tokens are handled with their own errors in the scanner if (token() === SyntaxKind.Unknown) { return; } // Otherwise, we know this some kind of unknown word, not just a missing expected semicolon. parseErrorAt(pos, node.end, Diagnostics.Unexpected_keyword_or_identifier); } /** * Reports a diagnostic error for the current token being an invalid name. * * @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName). * @param nameDiagnostic Diagnostic to report for all other cases. * @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped). */ function parseErrorForInvalidName(nameDiagnostic: DiagnosticMessage, blankDiagnostic: DiagnosticMessage, tokenIfBlankName: SyntaxKind) { if (token() === tokenIfBlankName) { parseErrorAtCurrentToken(blankDiagnostic); } else { parseErrorAtCurrentToken(nameDiagnostic, scanner.getTokenValue()); } } function getSpaceSuggestion(expressionText: string) { for (const keyword of viableKeywordSuggestions) { if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) { return `${keyword} ${expressionText.slice(keyword.length)}`; } } return undefined; } function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) { if (token() === SyntaxKind.AtToken && !scanner.hasPrecedingLineBreak()) { parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_the_name_and_all_keywords_of_property_declarations); return; } if (token() === SyntaxKind.OpenParenToken) { parseErrorAtCurrentToken(Diagnostics.Cannot_start_a_function_call_in_a_type_annotation); nextToken(); return; } if (type && !canParseSemicolon()) { if (initializer) { parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); } else { parseErrorAtCurrentToken(Diagnostics.Expected_for_property_initializer); } return; } if (tryParseSemicolon()) { return; } if (initializer) { parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken)); return; } parseErrorForMissingSemicolonAfter(name); } function parseExpectedJSDoc(kind: JSDocSyntaxKind) { if (token() === kind) { nextTokenJSDoc(); return true; } parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind)); return false; } function parseExpectedMatchingBrackets(openKind: SyntaxKind, closeKind: SyntaxKind, openParsed: boolean, openPosition: number) { if (token() === closeKind) { nextToken(); return; } const lastError = parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(closeKind)); if (!openParsed) { return; } if (lastError) { addRelatedInfo( lastError, createDetachedDiagnostic(fileName, openPosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, tokenToString(openKind), tokenToString(closeKind)) ); } } function parseOptional(t: SyntaxKind): boolean { if (token() === t) { nextToken(); return true; } return false; } function parseOptionalToken(t: TKind): Token; function parseOptionalToken(t: SyntaxKind): Node | undefined { if (token() === t) { return parseTokenNode(); } return undefined; } function parseOptionalTokenJSDoc(t: TKind): Token; function parseOptionalTokenJSDoc(t: JSDocSyntaxKind): Node | undefined { if (token() === t) { return parseTokenNodeJSDoc(); } return undefined; } function parseExpectedToken(t: TKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Token; function parseExpectedToken(t: SyntaxKind, diagnosticMessage?: DiagnosticMessage, arg0?: any): Node { return parseOptionalToken(t) || createMissingNode(t, /*reportAtCurrentPosition*/ false, diagnosticMessage || Diagnostics._0_expected, arg0 || tokenToString(t)); } function parseExpectedTokenJSDoc(t: TKind): Token; function parseExpectedTokenJSDoc(t: JSDocSyntaxKind): Node { return parseOptionalTokenJSDoc(t) || createMissingNode(t, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(t)); } function parseTokenNode(): T { const pos = getNodePos(); const kind = token(); nextToken(); return finishNode(factory.createToken(kind), pos) as T; } function parseTokenNodeJSDoc(): T { const pos = getNodePos(); const kind = token(); nextTokenJSDoc(); return finishNode(factory.createToken(kind), pos) as T; } 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 tryParseSemicolon() { if (!canParseSemicolon()) { return false; } if (token() === SyntaxKind.SemicolonToken) { // consume the semicolon if it was explicitly provided. nextToken(); } return true; } function parseSemicolon(): boolean { return tryParseSemicolon() || parseExpected(SyntaxKind.SemicolonToken); } function createNodeArray(elements: T[], pos: number, end?: number, hasTrailingComma?: boolean): NodeArray { const array = factory.createNodeArray(elements, hasTrailingComma); setTextRangePosEnd(array, pos, end ?? scanner.getStartPos()); return array; } function finishNode(node: T, pos: number, end?: number, virtual?: boolean): T { setTextRangePosEnd(node, pos, end ?? scanner.getStartPos()); if (contextFlags) { (node as Mutable).flags |= 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 as Mutable).flags |= NodeFlags.ThisNodeHasError; } if (virtual) { node.virtual = true; } return node; } function createMissingNode(kind: T["kind"], reportAtCurrentPosition: false, diagnosticMessage?: DiagnosticMessage, arg0?: any): T; function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T; function createMissingNode(kind: T["kind"], reportAtCurrentPosition: boolean, diagnosticMessage: DiagnosticMessage, arg0?: any): T { if (reportAtCurrentPosition) { parseErrorAtPosition(scanner.getStartPos(), 0, diagnosticMessage, arg0); } else if (diagnosticMessage) { parseErrorAtCurrentToken(diagnosticMessage, arg0); } const pos = getNodePos(); const result = kind === SyntaxKind.Identifier ? factory.createIdentifier("", /*typeArguments*/ undefined, /*originalKeywordKind*/ undefined) : isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, "", "", /*templateFlags*/ undefined) : kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral("", /*numericLiteralFlags*/ undefined) : kind === SyntaxKind.StringLiteral ? factory.createStringLiteral("", /*isSingleQuote*/ undefined) : kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : factory.createToken(kind); return finishNode(result, pos) as T; } function internIdentifier(text: string): string { let identifier = identifiers.get(text); if (identifier === undefined) { identifiers.set(text, identifier = text); } return identifier; } // 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, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { if (isIdentifier) { identifierCount++; const pos = getNodePos(); // Store original token kind if it is not just an Identifier so we can report appropriate error later in type checker const originalKeywordKind = token(); const text = internIdentifier(scanner.getTokenValue()); const hasExtendedUnicodeEscape = scanner.hasExtendedUnicodeEscape(); nextTokenWithoutCheck(); return finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind, hasExtendedUnicodeEscape), pos); } if (token() === SyntaxKind.PrivateIdentifier) { parseErrorAtCurrentToken(privateIdentifierDiagnosticMessage || Diagnostics.Private_identifiers_are_not_allowed_outside_class_bodies); return createIdentifier(/*isIdentifier*/ true); } if (token() === SyntaxKind.Unknown && scanner.tryScan(() => scanner.reScanInvalidIdentifier() === SyntaxKind.Identifier)) { // Scanner has already recorded an 'Invalid character' error, so no need to add another from the parser. return createIdentifier(/*isIdentifier*/ true); } if (stateStylesRootNode && inEtsStateStylesContext() && token() === SyntaxKind.DotToken) { identifierCount++; const pos = getNodePos(); return finishVirtualNode( factory.createIdentifier(`${stateStylesRootNode}Instance`, /*typeArguments*/ undefined, SyntaxKind.Identifier), pos, pos ); } identifierCount++; // Only for end of file because the error gets reported incorrectly on embedded script tags. const reportAtCurrentPosition = token() === SyntaxKind.EndOfFileToken; const isReservedWord = scanner.isReservedWord(); const msgArg = scanner.getTokenText(); const defaultMessage = isReservedWord ? Diagnostics.Identifier_expected_0_is_a_reserved_word_that_cannot_be_used_here : Diagnostics.Identifier_expected; return createMissingNode(SyntaxKind.Identifier, reportAtCurrentPosition, diagnosticMessage || defaultMessage, msgArg); } function parseBindingIdentifier(privateIdentifierDiagnosticMessage?: DiagnosticMessage) { return createIdentifier(isBindingIdentifier(), /*diagnosticMessage*/ undefined, privateIdentifierDiagnosticMessage); } function parseIdentifier(diagnosticMessage?: DiagnosticMessage, privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier { return createIdentifier(isIdentifier(), diagnosticMessage, privateIdentifierDiagnosticMessage); } function parseEtsIdentifier(pos: number): Identifier { identifierCount++; const text = internIdentifier(stylesEtsComponentDeclaration!.type); return finishVirtualNode(factory.createIdentifier(text), pos, pos); } function parseIdentifierName(diagnosticMessage?: DiagnosticMessage): Identifier { return createIdentifier(tokenIsIdentifierOrKeyword(token()), diagnosticMessage); } function isLiteralPropertyName(): boolean { return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral; } function isAssertionKey(): boolean { return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.StringLiteral; } function parsePropertyNameWorker(allowComputedPropertyNames: boolean): PropertyName { if (token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral) { const node = parseLiteralNode() as StringLiteral | NumericLiteral; node.text = internIdentifier(node.text); return node; } if (allowComputedPropertyNames && token() === SyntaxKind.OpenBracketToken) { return parseComputedPropertyName(); } if (token() === SyntaxKind.PrivateIdentifier) { return parsePrivateIdentifier(); } return parseIdentifierName(); } function parsePropertyName(): PropertyName { return parsePropertyNameWorker(/*allowComputedPropertyNames*/ true); } function parseComputedPropertyName(): ComputedPropertyName { // PropertyName [Yield]: // LiteralPropertyName // ComputedPropertyName[?Yield] const pos = getNodePos(); 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. const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseBracketToken); return finishNode(factory.createComputedPropertyName(expression), pos); } function internPrivateIdentifier(text: string): string { let privateIdentifier = privateIdentifiers.get(text); if (privateIdentifier === undefined) { privateIdentifiers.set(text, privateIdentifier = text); } return privateIdentifier; } function parsePrivateIdentifier(): PrivateIdentifier { const pos = getNodePos(); const node = factory.createPrivateIdentifier(internPrivateIdentifier(scanner.getTokenValue())); nextToken(); return finishNode(node, pos); } function parseContextualModifier(t: SyntaxKind): boolean { return token() === t && tryParse(nextTokenCanFollowModifier); } function nextTokenIsOnSameLineAndCanFollowModifier() { nextToken(); if (scanner.hasPrecedingLineBreak()) { return false; } return canFollowModifier(); } function nextTokenCanFollowModifier() { switch (token()) { case SyntaxKind.ConstKeyword: // 'const' is only a modifier if followed by 'enum'. return nextToken() === SyntaxKind.EnumKeyword; case SyntaxKind.ExportKeyword: nextToken(); if (token() === SyntaxKind.DefaultKeyword) { return lookAhead(nextTokenCanFollowDefaultKeyword); } if (token() === SyntaxKind.TypeKeyword) { return lookAhead(nextTokenCanFollowExportModifier); } if (inAllowAnnotationContext() && token() === SyntaxKind.AtToken) { return lookAhead(() => nextToken() === SyntaxKind.InterfaceKeyword); } return canFollowExportModifier(); case SyntaxKind.DefaultKeyword: return nextTokenCanFollowDefaultKeyword(); case SyntaxKind.AccessorKeyword: case SyntaxKind.StaticKeyword: case SyntaxKind.GetKeyword: case SyntaxKind.SetKeyword: nextToken(); return canFollowModifier(); default: return nextTokenIsOnSameLineAndCanFollowModifier(); } } function canFollowExportModifier(): boolean { return token() !== SyntaxKind.AsteriskToken && token() !== SyntaxKind.AsKeyword && token() !== SyntaxKind.OpenBraceToken && canFollowModifier(); } function nextTokenCanFollowExportModifier(): boolean { nextToken(); return canFollowExportModifier(); } function parseAnyContextualModifier(): boolean { return isModifierKind(token()) && tryParse(nextTokenCanFollowModifier); } function canFollowModifier(): boolean { return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.DotDotDotToken || token() === SyntaxKind.AtToken && inAllowAnnotationContext() && lookAhead(() => nextToken() === SyntaxKind.InterfaceKeyword) || isLiteralPropertyName(); } function nextTokenCanFollowDefaultKeyword(): boolean { nextToken(); return token() === SyntaxKind.ClassKeyword || (inEtsContext() && token() === SyntaxKind.StructKeyword) || token() === SyntaxKind.FunctionKeyword || token() === SyntaxKind.InterfaceKeyword || (token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsClassKeywordOnSameLine)) || (token() === SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsFunctionKeywordOnSameLine)); } // True if positioned at the start of a list element function isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { const node = currentNode(parsingContext); if (node) { return true; } switch (parsingContext) { case ParsingContext.SourceElements: case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: // 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) && isStartOfStatement(); case ParsingContext.SwitchClauses: return token() === SyntaxKind.CaseKeyword || token() === SyntaxKind.DefaultKeyword; case ParsingContext.TypeMembers: return lookAhead(isTypeMemberStart); 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.AnnotationMembers: return lookAhead(isAnnotationMemberStart); 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: switch (token()) { case SyntaxKind.OpenBracketToken: case SyntaxKind.AsteriskToken: case SyntaxKind.DotDotDotToken: case SyntaxKind.DotToken: // Not an object literal member, but don't want to close the object (see `tests/cases/fourslash/completionsDotInObjectLiteral.ts`) return true; default: return isLiteralPropertyName(); } case ParsingContext.RestProperties: return isLiteralPropertyName(); case ParsingContext.ObjectBindingElements: return token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.DotDotDotToken || isLiteralPropertyName(); case ParsingContext.AssertEntries: return isAssertionKey(); 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 isBindingIdentifierOrPrivateIdentifierOrPattern(); case ParsingContext.ArrayBindingElements: return token() === SyntaxKind.CommaToken || token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern(); case ParsingContext.TypeParameters: return token() === SyntaxKind.InKeyword || isIdentifier(); case ParsingContext.ArrayLiteralMembers: switch (token()) { case SyntaxKind.CommaToken: case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) return true; } // falls through case ParsingContext.ArgumentExpressions: return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: return isStartOfParameter(/*isJSDocParameter*/ false); case ParsingContext.JSDocParameters: return isStartOfParameter(/*isJSDocParameter*/ true); case ParsingContext.TypeArguments: case ParsingContext.TupleElementTypes: return token() === SyntaxKind.CommaToken || isStartOfType(); case ParsingContext.HeritageClauses: return isHeritageClause(); case ParsingContext.ImportOrExportSpecifiers: return tokenIsIdentifierOrKeyword(token()); case ParsingContext.JsxAttributes: return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken; case ParsingContext.JsxChildren: return true; } return 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 const next = nextToken(); return next === SyntaxKind.CommaToken || next === SyntaxKind.OpenBraceToken || next === SyntaxKind.ExtendsKeyword || next === SyntaxKind.ImplementsKeyword; } return true; } function nextTokenIsIdentifier() { nextToken(); return isIdentifier(); } function nextTokenIsIdentifierOrKeyword() { nextToken(); return tokenIsIdentifierOrKeyword(token()); } function nextTokenIsIdentifierOrKeywordOrGreaterThan() { nextToken(); return tokenIsIdentifierOrKeywordOrGreaterThan(token()); } function isHeritageClauseExtendsOrImplementsKeyword(): boolean { if (token() === SyntaxKind.ImplementsKeyword || token() === SyntaxKind.ExtendsKeyword) { return lookAhead(nextTokenIsStartOfExpression); } return false; } function nextTokenIsStartOfExpression() { nextToken(); return isStartOfExpression(); } function nextTokenIsStartOfType() { nextToken(); return isStartOfType(); } // 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.BlockStatements: case ParsingContext.SwitchClauses: case ParsingContext.TypeMembers: case ParsingContext.ClassMembers: case ParsingContext.AnnotationMembers: case ParsingContext.EnumMembers: case ParsingContext.ObjectLiteralMembers: case ParsingContext.ObjectBindingElements: case ParsingContext.ImportOrExportSpecifiers: case ParsingContext.AssertEntries: 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.JSDocParameters: case ParsingContext.Parameters: case ParsingContext.RestProperties: // 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: // All other tokens should cause the type-argument to terminate except comma token return token() !== SyntaxKind.CommaToken; case ParsingContext.HeritageClauses: return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.CloseBraceToken; case ParsingContext.JsxAttributes: return token() === SyntaxKind.GreaterThanToken || token() === SyntaxKind.SlashToken; case ParsingContext.JsxChildren: return token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsSlash); default: return false; } } 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(kind: ParsingContext, parseElement: () => T): NodeArray { const saveParsingContext = parsingContext; parsingContext |= 1 << kind; const list = []; const listPos = getNodePos(); while (!isListTerminator(kind)) { if (isListElement(kind, /*inErrorRecovery*/ false)) { list.push(parseListElement(kind, parseElement)); continue; } if (abortParsingListOrMoveToNextToken(kind)) { break; } } parsingContext = saveParsingContext; return createNodeArray(list, listPos); } function parseListElement(parsingContext: ParsingContext, parseElement: () => T): T { const node = currentNode(parsingContext); if (node) { return consumeNode(node) as T; } return parseElement(); } function currentNode(parsingContext: ParsingContext, pos?: number): Node | undefined { // If we don't have a cursor or the parsing context isn't reusable, there's nothing to reuse. // // 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 (!syntaxCursor || !isReusableParsingContext(parsingContext) || parseErrorBeforeNextFinishedNode) { return undefined; } const node = syntaxCursor.currentNode(pos ?? scanner.getStartPos()); // Can't reuse a missing node. // Can't reuse a node that intersected the change range. // Can't reuse a node that contains a parse error. This is necessary so that we // produce the same set of errors again. if (nodeIsMissing(node) || node.intersectsChange || 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 presence of strict mode may cause us to parse the tokens in the file // differently. // // 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. const nodeContextFlags = node.flags & NodeFlags.ContextFlags; 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 current list parsing context that we're currently at. if (!canReuseNode(node, parsingContext)) { return undefined; } if ((node as JSDocContainer).jsDocCache) { // jsDocCache may include tags from parent nodes, which might have been modified. (node as JSDocContainer).jsDocCache = 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 isReusableParsingContext(parsingContext: ParsingContext): boolean { switch (parsingContext) { case ParsingContext.ClassMembers: case ParsingContext.SwitchClauses: case ParsingContext.SourceElements: case ParsingContext.BlockStatements: case ParsingContext.SwitchClauseStatements: case ParsingContext.EnumMembers: case ParsingContext.TypeMembers: case ParsingContext.VariableDeclarations: case ParsingContext.JSDocParameters: case ParsingContext.Parameters: return true; } return false; } function canReuseNode(node: Node, parsingContext: ParsingContext): boolean { switch (parsingContext) { case ParsingContext.ClassMembers: return isReusableClassMember(node); case ParsingContext.SwitchClauses: return isReusableSwitchClause(node); case ParsingContext.SourceElements: 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.JSDocParameters: 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: // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes // on any given element. Same for children. // case ParsingContext.JsxAttributes: // case ParsingContext.JsxChildren: } return false; } function isReusableClassMember(node: Node) { if (node) { switch (node.kind) { case SyntaxKind.Constructor: case SyntaxKind.IndexSignature: case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.SemicolonClassElement: return true; case SyntaxKind.PropertyDeclaration: return !inStructContext(); case SyntaxKind.MethodDeclaration: // Method declarations are not necessarily reusable. An object-literal // may have a method calls "constructor(...)" and we must reparse that // into an actual .ConstructorDeclaration. const methodDeclaration = node as MethodDeclaration; const nameIsConstructor = methodDeclaration.name.kind === SyntaxKind.Identifier && methodDeclaration.name.originalKeywordKind === SyntaxKind.ConstructorKeyword; return !nameIsConstructor; } } 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: case SyntaxKind.ImportDeclaration: case SyntaxKind.ImportEqualsDeclaration: case SyntaxKind.ExportDeclaration: case SyntaxKind.ExportAssignment: case SyntaxKind.ModuleDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.TypeAliasDeclaration: 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() // // then we have a problem. "v = new List(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray; function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray> | undefined; function parseDelimitedList(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimiter?: boolean): NodeArray> | undefined { const saveParsingContext = parsingContext; parsingContext |= 1 << kind; const list: NonNullable[] = []; const listPos = getNodePos(); let commaStart = -1; // Meaning the previous token was not a comma while (true) { if (isListElement(kind, /*inErrorRecovery*/ false)) { const startPos = scanner.getStartPos(); const result = parseListElement(kind, parseElement); if (!result) { parsingContext = saveParsingContext; return undefined; } list.push(result); commaStart = scanner.getTokenPos(); if (parseOptional(SyntaxKind.CommaToken)) { // No need to check for a zero length node since we know we parsed a comma 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, getExpectedCommaDiagnostic(kind)); // 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 (considerSemicolonAsDelimiter && token() === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) { nextToken(); } if (startPos === scanner.getStartPos()) { // What we're parsing isn't actually remotely recognizable as a element and we've consumed no tokens whatsoever // Consume a token to advance the parser in some way and avoid an infinite loop // This can happen when we're speculatively parsing parenthesized expressions which we think may be arrow functions, // or when a modifier keyword which is disallowed as a parameter name (ie, `static` in strict mode) is supplied nextToken(); } continue; } if (isListTerminator(kind)) { break; } if (abortParsingListOrMoveToNextToken(kind)) { break; } } parsingContext = saveParsingContext; // 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. // Always preserve a trailing comma by marking it on the NodeArray return createNodeArray(list, listPos, /*end*/ undefined, commaStart >= 0); } function getExpectedCommaDiagnostic(kind: ParsingContext) { return kind === ParsingContext.EnumMembers ? Diagnostics.An_enum_member_name_must_be_followed_by_a_or : undefined; } interface MissingList extends NodeArray { isMissingList: true; } function createMissingList(): MissingList { const list = createNodeArray([], getNodePos()) as MissingList; list.isMissingList = true; return list; } function isMissingList(arr: NodeArray): boolean { return !!(arr as MissingList).isMissingList; } function parseBracketedList(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray { if (parseExpected(open)) { const result = parseDelimitedList(kind, parseElement); parseExpected(close); return result; } return createMissingList(); } function parseEntityName(allowReservedWords: boolean, diagnosticMessage?: DiagnosticMessage): EntityName { const pos = getNodePos(); let entity: EntityName = allowReservedWords ? parseIdentifierName(diagnosticMessage) : parseIdentifier(diagnosticMessage); let dotPos = getNodePos(); while (parseOptional(SyntaxKind.DotToken)) { if (token() === SyntaxKind.LessThanToken) { // the entity is part of a JSDoc-style generic, so record the trailing dot for later error reporting entity.jsdocDotPos = dotPos; break; } dotPos = getNodePos(); entity = finishNode( factory.createQualifiedName( entity, parseRightSideOfDot(allowReservedWords, /* allowPrivateIdentifiers */ false) as Identifier ), pos ); } return entity; } function createQualifiedName(entity: EntityName, name: Identifier): QualifiedName { return finishNode(factory.createQualifiedName(entity, name), entity.pos); } function parseRightSideOfDot(allowIdentifierNames: boolean, allowPrivateIdentifiers: boolean): Identifier | PrivateIdentifier { // Technically a keyword is valid here as all identifiers and keywords are identifier names. // However, often we'll encounter this in error situations when the identifier or keyword // is actually starting another valid construct. // // So, we check for the following specific case: // // name. // identifierOrKeyword identifierNameOrKeyword // // Note: the newlines are important here. For example, if that above code // were rewritten into: // // name.identifierOrKeyword // identifierNameOrKeyword // // Then we would consider it valid. That's because ASI would take effect and // the code would be implicitly: "name.identifierOrKeyword; identifierNameOrKeyword". // In the first case though, ASI will not take effect because there is not a // line terminator after the identifier or keyword. if (scanner.hasPrecedingLineBreak() && tokenIsIdentifierOrKeyword(token())) { const 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 would be quite confusing. return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); } } if (token() === SyntaxKind.PrivateIdentifier) { const node = parsePrivateIdentifier(); return allowPrivateIdentifiers ? node : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Identifier_expected); } return allowIdentifierNames ? parseIdentifierName() : parseIdentifier(); } function parseTemplateSpans(isTaggedTemplate: boolean) { const pos = getNodePos(); const list = []; let node: TemplateSpan; do { node = parseTemplateSpan(isTaggedTemplate); list.push(node); } while (node.literal.kind === SyntaxKind.TemplateMiddle); return createNodeArray(list, pos); } function parseTemplateExpression(isTaggedTemplate: boolean): TemplateExpression { const pos = getNodePos(); return finishNode( factory.createTemplateExpression( parseTemplateHead(isTaggedTemplate), parseTemplateSpans(isTaggedTemplate) ), pos ); } function parseTemplateType(): TemplateLiteralTypeNode { const pos = getNodePos(); return finishNode( factory.createTemplateLiteralType( parseTemplateHead(/*isTaggedTemplate*/ false), parseTemplateTypeSpans() ), pos ); } function parseTemplateTypeSpans() { const pos = getNodePos(); const list = []; let node: TemplateLiteralTypeSpan; do { node = parseTemplateTypeSpan(); list.push(node); } while (node.literal.kind === SyntaxKind.TemplateMiddle); return createNodeArray(list, pos); } function parseTemplateTypeSpan(): TemplateLiteralTypeSpan { const pos = getNodePos(); return finishNode( factory.createTemplateLiteralTypeSpan( parseType(), parseLiteralOfTemplateSpan(/*isTaggedTemplate*/ false) ), pos ); } function parseLiteralOfTemplateSpan(isTaggedTemplate: boolean) { if (token() === SyntaxKind.CloseBraceToken) { reScanTemplateToken(isTaggedTemplate); return parseTemplateMiddleOrTemplateTail(); } else { // TODO(rbuckton): Do we need to call `parseExpectedToken` or can we just call `createMissingNode` directly? return parseExpectedToken(SyntaxKind.TemplateTail, Diagnostics._0_expected, tokenToString(SyntaxKind.CloseBraceToken)) as TemplateTail; } } function parseTemplateSpan(isTaggedTemplate: boolean): TemplateSpan { const pos = getNodePos(); return finishNode( factory.createTemplateSpan( allowInAnd(parseExpression), parseLiteralOfTemplateSpan(isTaggedTemplate) ), pos ); } function parseLiteralNode(): LiteralExpression { return parseLiteralLikeNode(token()) as LiteralExpression; } function parseTemplateHead(isTaggedTemplate: boolean): TemplateHead { if (isTaggedTemplate) { reScanTemplateHeadOrNoSubstitutionTemplate(); } const fragment = parseLiteralLikeNode(token()); Debug.assert(fragment.kind === SyntaxKind.TemplateHead, "Template head has wrong token kind"); return fragment as TemplateHead; } function parseTemplateMiddleOrTemplateTail(): TemplateMiddle | TemplateTail { const fragment = parseLiteralLikeNode(token()); Debug.assert(fragment.kind === SyntaxKind.TemplateMiddle || fragment.kind === SyntaxKind.TemplateTail, "Template fragment has wrong token kind"); return fragment as TemplateMiddle | TemplateTail; } function getTemplateLiteralRawText(kind: TemplateLiteralToken["kind"]) { const isLast = kind === SyntaxKind.NoSubstitutionTemplateLiteral || kind === SyntaxKind.TemplateTail; const tokenText = scanner.getTokenText(); return tokenText.substring(1, tokenText.length - (scanner.isUnterminated() ? 0 : isLast ? 1 : 2)); } function parseLiteralLikeNode(kind: SyntaxKind): LiteralLikeNode { const pos = getNodePos(); const node = isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, scanner.getTokenValue(), getTemplateLiteralRawText(kind), scanner.getTokenFlags() & TokenFlags.TemplateLiteralLikeFlags) : // 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. kind === SyntaxKind.NumericLiteral ? factory.createNumericLiteral(scanner.getTokenValue(), scanner.getNumericLiteralFlags()) : kind === SyntaxKind.StringLiteral ? factory.createStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : isLiteralKind(kind) ? factory.createLiteralLikeNode(kind, scanner.getTokenValue()) : Debug.fail(); if (scanner.hasExtendedUnicodeEscape()) { node.hasExtendedUnicodeEscape = true; } if (scanner.isUnterminated()) { node.isUnterminated = true; } nextToken(); return finishNode(node, pos); } // TYPES function parseEntityNameOfTypeReference() { return parseEntityName(/*allowReservedWords*/ true, Diagnostics.Type_expected); } function parseTypeArgumentsOfTypeReference() { if (!scanner.hasPrecedingLineBreak() && reScanLessThanToken() === SyntaxKind.LessThanToken) { return parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } } function parseTypeReference(): TypeReferenceNode { const pos = getNodePos(); return finishNode( factory.createTypeReferenceNode( parseEntityNameOfTypeReference(), parseTypeArgumentsOfTypeReference() ), pos ); } // If true, we should abort parsing an error function. function typeHasArrowFunctionBlockingParseError(node: TypeNode): boolean { switch (node.kind) { case SyntaxKind.TypeReference: return nodeIsMissing((node as TypeReferenceNode).typeName); case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: { const { parameters, type } = node as FunctionOrConstructorTypeNode; return isMissingList(parameters) || typeHasArrowFunctionBlockingParseError(type); } case SyntaxKind.ParenthesizedType: return typeHasArrowFunctionBlockingParseError((node as ParenthesizedTypeNode).type); default: return false; } } function parseThisTypePredicate(lhs: ThisTypeNode): TypePredicateNode { nextToken(); return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, lhs, parseType()), lhs.pos); } function parseThisTypeNode(): ThisTypeNode { const pos = getNodePos(); nextToken(); return finishNode(factory.createThisTypeNode(), pos); } function parseJSDocAllType(): JSDocAllType | JSDocOptionalType { const pos = getNodePos(); nextToken(); return finishNode(factory.createJSDocAllType(), pos); } function parseJSDocNonNullableType(): TypeNode { const pos = getNodePos(); nextToken(); return finishNode(factory.createJSDocNonNullableType(parseNonArrayType(), /*postfix*/ false), pos); } function parseJSDocUnknownOrNullableType(): JSDocUnknownType | JSDocNullableType { const pos = getNodePos(); // 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) { return finishNode(factory.createJSDocUnknownType(), pos); } else { return finishNode(factory.createJSDocNullableType(parseType(), /*postfix*/ false), pos); } } function parseJSDocFunctionType(): JSDocFunctionType | TypeReferenceNode { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); if (lookAhead(nextTokenIsOpenParen)) { nextToken(); const parameters = parseParameters(SignatureFlags.Type | SignatureFlags.JSDoc); const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); return withJSDoc(finishNode(factory.createJSDocFunctionType(parameters, type), pos), hasJSDoc); } return finishNode(factory.createTypeReferenceNode(parseIdentifierName(), /*typeArguments*/ undefined), pos); } function parseJSDocParameter(): ParameterDeclaration { const pos = getNodePos(); let name: Identifier | undefined; if (token() === SyntaxKind.ThisKeyword || token() === SyntaxKind.NewKeyword) { name = parseIdentifierName(); parseExpected(SyntaxKind.ColonToken); } return finishNode( factory.createParameterDeclaration( /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, // TODO(rbuckton): JSDoc parameters don't have names (except `this`/`new`), should we manufacture an empty identifier? name!, /*questionToken*/ undefined, parseJSDocType(), /*initializer*/ undefined ), pos ); } function parseJSDocType(): TypeNode { scanner.setInJSDocType(true); const pos = getNodePos(); if (parseOptional(SyntaxKind.ModuleKeyword)) { // TODO(rbuckton): We never set the type for a JSDocNamepathType. What should we put here? const moduleTag = factory.createJSDocNamepathType(/*type*/ undefined!); terminate: while (true) { switch (token()) { case SyntaxKind.CloseBraceToken: case SyntaxKind.EndOfFileToken: case SyntaxKind.CommaToken: case SyntaxKind.WhitespaceTrivia: break terminate; default: nextTokenJSDoc(); } } scanner.setInJSDocType(false); return finishNode(moduleTag, pos); } const hasDotDotDot = parseOptional(SyntaxKind.DotDotDotToken); let type = parseTypeOrTypePredicate(); scanner.setInJSDocType(false); if (hasDotDotDot) { type = finishNode(factory.createJSDocVariadicType(type), pos); } if (token() === SyntaxKind.EqualsToken) { nextToken(); return finishNode(factory.createJSDocOptionalType(type), pos); } return type; } function parseTypeQuery(): TypeQueryNode { const pos = getNodePos(); parseExpected(SyntaxKind.TypeOfKeyword); const entityName = parseEntityName(/*allowReservedWords*/ true); // Make sure we perform ASI to prevent parsing the next line's type arguments as part of an instantiation expression. const typeArguments = !scanner.hasPrecedingLineBreak() ? tryParseTypeArguments() : undefined; return finishNode(factory.createTypeQueryNode(entityName, typeArguments), pos); } function parseTypeParameter(position?: number): TypeParameterDeclaration { const pos = getNodePos(); const modifiers = parseModifiers(); const name = position !== undefined ? parseEtsIdentifier(position) : parseIdentifier(); let constraint: TypeNode | undefined; let expression: Expression | undefined; 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()) { 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: // // // // We do *not* want to consume the `>` as we're consuming the expression for "". expression = parseUnaryExpressionOrHigher(); } } const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined; const node = factory.createTypeParameterDeclaration(modifiers, name, constraint, defaultType); node.expression = expression; return position !== undefined ? finishVirtualNode(node, position, position) : finishNode(node, pos); } function parseTypeParameters(): NodeArray | undefined { if (token() === SyntaxKind.LessThanToken) { return parseBracketedList(ParsingContext.TypeParameters, parseTypeParameter, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); } } function parseEtsTypeParameters(pos: number): NodeArray | undefined { return createNodeArray([parseTypeParameter(pos)], getNodePos()); } function parseEtsTypeArguments(pos: number, name: string): NodeArray | undefined { if ((contextFlags & NodeFlags.JavaScriptFile) !== 0) { // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. return undefined; } return createNodeArray([parseEtsType(pos, name)], getNodePos()); } function isStartOfParameter(isJSDocParameter: boolean): boolean { return token() === SyntaxKind.DotDotDotToken || isBindingIdentifierOrPrivateIdentifierOrPattern() || isModifierKind(token()) || token() === SyntaxKind.AtToken || isStartOfType(/*inStartOfParameter*/ !isJSDocParameter); } function parseNameOfParameter(modifiers: NodeArray | undefined) { // FormalParameter [Yield,Await]: // BindingElement[?Yield,?Await] const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_cannot_be_used_as_parameters); if (getFullWidth(name) === 0 && !some(modifiers) && isModifierKind(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(); } return name; } function isParameterNameStart() { // Be permissive about await and yield by calling isBindingIdentifier instead of isIdentifier; disallowing // them during a speculative parse leads to many more follow-on errors than allowing the function to parse then later // complaining about the use of the keywords. return isBindingIdentifier() || token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken; } function parseParameter(inOuterAwaitContext: boolean): ParameterDeclaration { return parseParameterWorker(inOuterAwaitContext); } function parseParameterForSpeculation(inOuterAwaitContext: boolean): ParameterDeclaration | undefined { return parseParameterWorker(inOuterAwaitContext, /*allowAmbiguity*/ false); } function parseParameterWorker(inOuterAwaitContext: boolean): ParameterDeclaration; function parseParameterWorker(inOuterAwaitContext: boolean, allowAmbiguity: false): ParameterDeclaration | undefined; function parseParameterWorker(inOuterAwaitContext: boolean, allowAmbiguity = true): ParameterDeclaration | undefined { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); // FormalParameter [Yield,Await]: // BindingElement[?Yield,?Await] // Decorators are parsed in the outer [Await] context, the rest of the parameter is parsed in the function's [Await] context. const decorators = inOuterAwaitContext ? doInAwaitContext(parseDecorators) : doOutsideOfAwaitContext(parseDecorators); if (token() === SyntaxKind.ThisKeyword) { const node = factory.createParameterDeclaration( decorators, /*dotDotDotToken*/ undefined, createIdentifier(/*isIdentifier*/ true), /*questionToken*/ undefined, parseTypeAnnotation(), /*initializer*/ undefined ); if (decorators) { parseErrorAtRange(decorators[0], Diagnostics.Decorators_may_not_be_applied_to_this_parameters); } return withJSDoc(finishNode(node, pos), hasJSDoc); } const savedTopLevel = topLevel; topLevel = false; const modifiers = combineDecoratorsAndModifiers(decorators, parseModifiers()); const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); if (!allowAmbiguity && !isParameterNameStart()) { return undefined; } const node = withJSDoc( finishNode( factory.createParameterDeclaration( modifiers, dotDotDotToken, parseNameOfParameter(modifiers), parseOptionalToken(SyntaxKind.QuestionToken), parseTypeAnnotation(), parseInitializer() ), pos ), hasJSDoc ); topLevel = savedTopLevel; return node; } function parseReturnType(returnToken: SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode; function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): TypeNode | undefined; function parseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean) { if (shouldParseReturnType(returnToken, isType)) { return allowConditionalTypesAnd(parseTypeOrTypePredicate); } } function shouldParseReturnType(returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken, isType: boolean): boolean { if (returnToken === SyntaxKind.EqualsGreaterThanToken) { parseExpected(returnToken); return true; } else if (parseOptional(SyntaxKind.ColonToken)) { return true; } else if (isType && token() === SyntaxKind.EqualsGreaterThanToken) { // This is easy to get backward, especially in type contexts, so parse the type anyway parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)); nextToken(); return true; } return false; } function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: true): NodeArray; function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: false): NodeArray | undefined; function parseParametersWorker(flags: SignatureFlags, allowAmbiguity: boolean): NodeArray | undefined { // FormalParameters [Yield,Await]: (modified) // [empty] // FormalParameterList[?Yield,Await] // // FormalParameter[Yield,Await]: (modified) // BindingElement[?Yield,Await] // // BindingElement [Yield,Await]: (modified) // SingleNameBinding[?Yield,?Await] // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt // // SingleNameBinding [Yield,Await]: // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt const savedYieldContext = inYieldContext(); const savedAwaitContext = inAwaitContext(); setYieldContext(!!(flags & SignatureFlags.Yield)); setAwaitContext(!!(flags & SignatureFlags.Await)); const parameters = flags & SignatureFlags.JSDoc ? parseDelimitedList(ParsingContext.JSDocParameters, parseJSDocParameter) : parseDelimitedList(ParsingContext.Parameters, () => allowAmbiguity ? parseParameter(savedAwaitContext) : parseParameterForSpeculation(savedAwaitContext)); setYieldContext(savedYieldContext); setAwaitContext(savedAwaitContext); return parameters; } function parseParameters(flags: SignatureFlags): NodeArray { // FormalParameters [Yield,Await]: (modified) // [empty] // FormalParameterList[?Yield,Await] // // FormalParameter[Yield,Await]: (modified) // BindingElement[?Yield,Await] // // BindingElement [Yield,Await]: (modified) // SingleNameBinding[?Yield,?Await] // BindingPattern[?Yield,?Await]Initializer [In, ?Yield,?Await] opt // // SingleNameBinding [Yield,Await]: // BindingIdentifier[?Yield,?Await]Initializer [In, ?Yield,?Await] opt if (!parseExpected(SyntaxKind.OpenParenToken)) { return createMissingList(); } const parameters = parseParametersWorker(flags, /*allowAmbiguity*/ true); parseExpected(SyntaxKind.CloseParenToken); return parameters; } 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.CallSignature | SyntaxKind.ConstructSignature): CallSignatureDeclaration | ConstructSignatureDeclaration { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); if (kind === SyntaxKind.ConstructSignature) { parseExpected(SyntaxKind.NewKeyword); } const typeParameters = parseTypeParameters(); const parameters = parseParameters(SignatureFlags.Type); const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); parseTypeMemberSemicolon(); const node = kind === SyntaxKind.CallSignature ? factory.createCallSignature(typeParameters, parameters, type) : factory.createConstructSignature(typeParameters, parameters, type); return withJSDoc(finishNode(node, pos), hasJSDoc); } function isIndexSignature(): boolean { return token() === SyntaxKind.OpenBracketToken && 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 (isModifierKind(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(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): IndexSignatureDeclaration { const parameters = parseBracketedList(ParsingContext.Parameters, () => parseParameter(/*inOuterAwaitContext*/ false), SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); const type = parseTypeAnnotation(); parseTypeMemberSemicolon(); const node = factory.createIndexSignature(modifiers, parameters, type); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parsePropertyOrMethodSignature(pos: number, hasJSDoc: boolean, modifiers: NodeArray | undefined): PropertySignature | MethodSignature { const name = parsePropertyName(); const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); let node: PropertySignature | MethodSignature; if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { // Method signatures don't exist in expression contexts. So they have neither // [Yield] nor [Await] const typeParameters = parseTypeParameters(); const parameters = parseParameters(SignatureFlags.Type); const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ true); node = factory.createMethodSignature(modifiers, name, questionToken, typeParameters, parameters, type); } else { const type = parseTypeAnnotation(); node = factory.createPropertySignature(modifiers, name, questionToken, type); // Although type literal properties cannot not have initializers, we attempt // to parse an initializer so we can report in the checker that an interface // property or type literal property cannot have an initializer. if (token() === SyntaxKind.EqualsToken) (node as Mutable).initializer = parseInitializer(); } parseTypeMemberSemicolon(); return withJSDoc(finishNode(node, pos), hasJSDoc); } function isTypeMemberStart(): boolean { // Return true if we have the start of a signature member if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || token() === SyntaxKind.GetKeyword || token() === SyntaxKind.SetKeyword) { return true; } let idToken = false; // Eat up all modifiers, but hold on to the last one in case it is actually an identifier while (isModifierKind(token())) { idToken = true; nextToken(); } // Index signatures and computed property names are type members if (token() === SyntaxKind.OpenBracketToken) { return true; } // Try to get the first property-like token following all modifiers if (isLiteralPropertyName()) { idToken = true; nextToken(); } // If we were able to get any potential identifier, check that it is // the start of a member declaration if (idToken) { return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken || token() === SyntaxKind.QuestionToken || token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || canParseSemicolon(); } return false; } function parseTypeMember(): TypeElement { if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { return parseSignatureMember(SyntaxKind.CallSignature); } if (token() === SyntaxKind.NewKeyword && lookAhead(nextTokenIsOpenParenOrLessThan)) { return parseSignatureMember(SyntaxKind.ConstructSignature); } const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const modifiers = parseModifiers(); if (parseContextualModifier(SyntaxKind.GetKeyword)) { return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.GetAccessor, SignatureFlags.Type); } if (parseContextualModifier(SyntaxKind.SetKeyword)) { return parseAccessorDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers, SyntaxKind.SetAccessor, SignatureFlags.Type); } if (isIndexSignature()) { return parseIndexSignatureDeclaration(pos, hasJSDoc, /*decorators*/ undefined, modifiers); } return parsePropertyOrMethodSignature(pos, hasJSDoc, modifiers); } function nextTokenIsOpenParenOrLessThan() { nextToken(); return token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken; } function nextTokenIsDot() { return nextToken() === SyntaxKind.DotToken; } function nextTokenIsOpenParenOrLessThanOrDot() { switch (nextToken()) { case SyntaxKind.OpenParenToken: case SyntaxKind.LessThanToken: case SyntaxKind.DotToken: return true; } return false; } function parseTypeLiteral(): TypeLiteralNode { const pos = getNodePos(); return finishNode(factory.createTypeLiteralNode(parseObjectTypeMembers()), pos); } function parseObjectTypeMembers(): NodeArray { let members: NodeArray; if (parseExpected(SyntaxKind.OpenBraceToken)) { members = parseList(ParsingContext.TypeMembers, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); } else { members = createMissingList(); } return members; } function isStartOfMappedType() { nextToken(); if (token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { return nextToken() === SyntaxKind.ReadonlyKeyword; } if (token() === SyntaxKind.ReadonlyKeyword) { nextToken(); } return token() === SyntaxKind.OpenBracketToken && nextTokenIsIdentifier() && nextToken() === SyntaxKind.InKeyword; } function parseMappedTypeParameter() { const pos = getNodePos(); const name = parseIdentifierName(); parseExpected(SyntaxKind.InKeyword); const type = parseType(); return finishNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, type, /*defaultType*/ undefined), pos); } function parseMappedType() { const pos = getNodePos(); parseExpected(SyntaxKind.OpenBraceToken); let readonlyToken: ReadonlyKeyword | PlusToken | MinusToken | undefined; if (token() === SyntaxKind.ReadonlyKeyword || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { readonlyToken = parseTokenNode(); if (readonlyToken.kind !== SyntaxKind.ReadonlyKeyword) { parseExpected(SyntaxKind.ReadonlyKeyword); } } parseExpected(SyntaxKind.OpenBracketToken); const typeParameter = parseMappedTypeParameter(); const nameType = parseOptional(SyntaxKind.AsKeyword) ? parseType() : undefined; parseExpected(SyntaxKind.CloseBracketToken); let questionToken: QuestionToken | PlusToken | MinusToken | undefined; if (token() === SyntaxKind.QuestionToken || token() === SyntaxKind.PlusToken || token() === SyntaxKind.MinusToken) { questionToken = parseTokenNode(); if (questionToken.kind !== SyntaxKind.QuestionToken) { parseExpected(SyntaxKind.QuestionToken); } } const type = parseTypeAnnotation(); parseSemicolon(); const members = parseList(ParsingContext.TypeMembers, parseTypeMember); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(factory.createMappedTypeNode(readonlyToken, typeParameter, nameType, questionToken, type, members), pos); } function parseTupleElementType() { const pos = getNodePos(); if (parseOptional(SyntaxKind.DotDotDotToken)) { return finishNode(factory.createRestTypeNode(parseType()), pos); } const type = parseType(); if (isJSDocNullableType(type) && type.pos === type.type.pos) { const node = factory.createOptionalTypeNode(type.type); setTextRange(node, type); (node as Mutable).flags = type.flags; return node; } return type; } function isNextTokenColonOrQuestionColon() { return nextToken() === SyntaxKind.ColonToken || (token() === SyntaxKind.QuestionToken && nextToken() === SyntaxKind.ColonToken); } function isTupleElementName() { if (token() === SyntaxKind.DotDotDotToken) { return tokenIsIdentifierOrKeyword(nextToken()) && isNextTokenColonOrQuestionColon(); } return tokenIsIdentifierOrKeyword(token()) && isNextTokenColonOrQuestionColon(); } function parseTupleElementNameOrTupleElementType() { if (lookAhead(isTupleElementName)) { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); const name = parseIdentifierName(); const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); parseExpected(SyntaxKind.ColonToken); const type = parseTupleElementType(); const node = factory.createNamedTupleMember(dotDotDotToken, name, questionToken, type); return withJSDoc(finishNode(node, pos), hasJSDoc); } return parseTupleElementType(); } function parseTupleType(): TupleTypeNode { const pos = getNodePos(); return finishNode( factory.createTupleTypeNode( parseBracketedList(ParsingContext.TupleElementTypes, parseTupleElementNameOrTupleElementType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken) ), pos ); } function parseParenthesizedType(): TypeNode { const pos = getNodePos(); parseExpected(SyntaxKind.OpenParenToken); const type = parseType(); parseExpected(SyntaxKind.CloseParenToken); return finishNode(factory.createParenthesizedType(type), pos); } function parseModifiersForConstructorType(): NodeArray | undefined { let modifiers: NodeArray | undefined; if (token() === SyntaxKind.AbstractKeyword) { const pos = getNodePos(); nextToken(); const modifier = finishNode(factory.createToken(SyntaxKind.AbstractKeyword), pos); modifiers = createNodeArray([modifier], pos); } return modifiers; } function parseFunctionOrConstructorType(): TypeNode { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const modifiers = parseModifiersForConstructorType(); const isConstructorType = parseOptional(SyntaxKind.NewKeyword); const typeParameters = parseTypeParameters(); const parameters = parseParameters(SignatureFlags.Type); const type = parseReturnType(SyntaxKind.EqualsGreaterThanToken, /*isType*/ false); const node = isConstructorType ? factory.createConstructorTypeNode(modifiers, typeParameters, parameters, type) : factory.createFunctionTypeNode(typeParameters, parameters, type); if (!isConstructorType) (node as Mutable).modifiers = modifiers; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseKeywordAndNoDot(): TypeNode | undefined { const node = parseTokenNode(); return token() === SyntaxKind.DotToken ? undefined : node; } function parseLiteralTypeNode(negative?: boolean): LiteralTypeNode { const pos = getNodePos(); if (negative) { nextToken(); } let expression: BooleanLiteral | NullLiteral | LiteralExpression | PrefixUnaryExpression = token() === SyntaxKind.TrueKeyword || token() === SyntaxKind.FalseKeyword || token() === SyntaxKind.NullKeyword ? parseTokenNode() : parseLiteralLikeNode(token()) as LiteralExpression; if (negative) { expression = finishNode(factory.createPrefixUnaryExpression(SyntaxKind.MinusToken, expression), pos); } return finishNode(factory.createLiteralTypeNode(expression), pos); } function isStartOfTypeOfImportType() { nextToken(); return token() === SyntaxKind.ImportKeyword; } function parseImportTypeAssertions(): ImportTypeAssertionContainer { const pos = getNodePos(); const openBracePosition = scanner.getTokenPos(); parseExpected(SyntaxKind.OpenBraceToken); const multiLine = scanner.hasPrecedingLineBreak(); parseExpected(SyntaxKind.AssertKeyword); parseExpected(SyntaxKind.ColonToken); const clause = parseAssertClause(/*skipAssertKeyword*/ true); if (!parseExpected(SyntaxKind.CloseBraceToken)) { const lastError = lastOrUndefined(parseDiagnostics); if (lastError && lastError.code === Diagnostics._0_expected.code) { addRelatedInfo( lastError, createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, "{", "}") ); } } return finishNode(factory.createImportTypeAssertionContainer(clause, multiLine), pos); } function parseImportType(): ImportTypeNode { sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; const pos = getNodePos(); const isTypeOf = parseOptional(SyntaxKind.TypeOfKeyword); parseExpected(SyntaxKind.ImportKeyword); parseExpected(SyntaxKind.OpenParenToken); const type = parseType(); let assertions: ImportTypeAssertionContainer | undefined; if (parseOptional(SyntaxKind.CommaToken)) { assertions = parseImportTypeAssertions(); } parseExpected(SyntaxKind.CloseParenToken); const qualifier = parseOptional(SyntaxKind.DotToken) ? parseEntityNameOfTypeReference() : undefined; const typeArguments = parseTypeArgumentsOfTypeReference(); return finishNode(factory.createImportTypeNode(type, assertions, qualifier, typeArguments, isTypeOf), pos); } function nextTokenIsNumericOrBigIntLiteral() { nextToken(); return token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral; } function parseNonArrayType(): TypeNode { switch (token()) { case SyntaxKind.AnyKeyword: case SyntaxKind.UnknownKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.BigIntKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.UndefinedKeyword: case SyntaxKind.NeverKeyword: case SyntaxKind.ObjectKeyword: // If these are followed by a dot, then parse these out as a dotted type reference instead. return tryParse(parseKeywordAndNoDot) || parseTypeReference(); case SyntaxKind.AsteriskEqualsToken: // If there is '*=', treat it as * followed by postfix = scanner.reScanAsteriskEqualsToken(); // falls through case SyntaxKind.AsteriskToken: return parseJSDocAllType(); case SyntaxKind.QuestionQuestionToken: // If there is '??', treat it as prefix-'?' in JSDoc type. scanner.reScanQuestionToken(); // falls through case SyntaxKind.QuestionToken: return parseJSDocUnknownOrNullableType(); case SyntaxKind.FunctionKeyword: return parseJSDocFunctionType(); case SyntaxKind.ExclamationToken: return parseJSDocNonNullableType(); case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: return parseLiteralTypeNode(); case SyntaxKind.MinusToken: return lookAhead(nextTokenIsNumericOrBigIntLiteral) ? parseLiteralTypeNode(/*negative*/ true) : parseTypeReference(); case SyntaxKind.VoidKeyword: return parseTokenNode(); case SyntaxKind.ThisKeyword: { const thisKeyword = parseThisTypeNode(); if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { return parseThisTypePredicate(thisKeyword); } else { return thisKeyword; } } case SyntaxKind.TypeOfKeyword: return lookAhead(isStartOfTypeOfImportType) ? parseImportType() : parseTypeQuery(); case SyntaxKind.OpenBraceToken: return lookAhead(isStartOfMappedType) ? parseMappedType() : parseTypeLiteral(); case SyntaxKind.OpenBracketToken: return parseTupleType(); case SyntaxKind.OpenParenToken: return parseParenthesizedType(); case SyntaxKind.ImportKeyword: return parseImportType(); case SyntaxKind.AssertsKeyword: return lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine) ? parseAssertsTypePredicate() : parseTypeReference(); case SyntaxKind.TemplateHead: return parseTemplateType(); default: return parseTypeReference(); } } function isStartOfType(inStartOfParameter?: boolean): boolean { switch (token()) { case SyntaxKind.AnyKeyword: case SyntaxKind.UnknownKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.NumberKeyword: case SyntaxKind.BigIntKeyword: case SyntaxKind.BooleanKeyword: case SyntaxKind.ReadonlyKeyword: case SyntaxKind.SymbolKeyword: case SyntaxKind.UniqueKeyword: case SyntaxKind.VoidKeyword: case SyntaxKind.UndefinedKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.ThisKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.NeverKeyword: case SyntaxKind.OpenBraceToken: case SyntaxKind.OpenBracketToken: case SyntaxKind.LessThanToken: case SyntaxKind.BarToken: case SyntaxKind.AmpersandToken: case SyntaxKind.NewKeyword: case SyntaxKind.StringLiteral: case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.ObjectKeyword: case SyntaxKind.AsteriskToken: case SyntaxKind.QuestionToken: case SyntaxKind.ExclamationToken: case SyntaxKind.DotDotDotToken: case SyntaxKind.InferKeyword: case SyntaxKind.ImportKeyword: case SyntaxKind.AssertsKeyword: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateHead: return true; case SyntaxKind.FunctionKeyword: return !inStartOfParameter; case SyntaxKind.MinusToken: return !inStartOfParameter && lookAhead(nextTokenIsNumericOrBigIntLiteral); 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 !inStartOfParameter && lookAhead(isStartOfParenthesizedOrFunctionType); default: return isIdentifier(); } } function isStartOfParenthesizedOrFunctionType() { nextToken(); return token() === SyntaxKind.CloseParenToken || isStartOfParameter(/*isJSDocParameter*/ false) || isStartOfType(); } function parsePostfixTypeOrHigher(): TypeNode { const pos = getNodePos(); let type = parseNonArrayType(); while (!scanner.hasPrecedingLineBreak()) { switch (token()) { case SyntaxKind.ExclamationToken: nextToken(); type = finishNode(factory.createJSDocNonNullableType(type, /*postfix*/ true), pos); break; case SyntaxKind.QuestionToken: // If next token is start of a type we have a conditional type if (lookAhead(nextTokenIsStartOfType)) { return type; } nextToken(); type = finishNode(factory.createJSDocNullableType(type, /*postfix*/ true), pos); break; case SyntaxKind.OpenBracketToken: parseExpected(SyntaxKind.OpenBracketToken); if (isStartOfType()) { const indexType = parseType(); parseExpected(SyntaxKind.CloseBracketToken); type = finishNode(factory.createIndexedAccessTypeNode(type, indexType), pos); } else { parseExpected(SyntaxKind.CloseBracketToken); type = finishNode(factory.createArrayTypeNode(type), pos); } break; default: return type; } } return type; } function parseTypeOperator(operator: SyntaxKind.KeyOfKeyword | SyntaxKind.UniqueKeyword | SyntaxKind.ReadonlyKeyword) { const pos = getNodePos(); parseExpected(operator); return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos); } function tryParseConstraintOfInferType() { if (parseOptional(SyntaxKind.ExtendsKeyword)) { const constraint = disallowConditionalTypesAnd(parseType); if (inDisallowConditionalTypesContext() || token() !== SyntaxKind.QuestionToken) { return constraint; } } } function parseTypeParameterOfInferType(): TypeParameterDeclaration { const pos = getNodePos(); const name = parseIdentifier(); const constraint = tryParse(tryParseConstraintOfInferType); const node = factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, constraint); return finishNode(node, pos); } function parseInferType(): InferTypeNode { const pos = getNodePos(); parseExpected(SyntaxKind.InferKeyword); return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos); } function parseTypeOperatorOrHigher(): TypeNode { const operator = token(); switch (operator) { case SyntaxKind.KeyOfKeyword: case SyntaxKind.UniqueKeyword: case SyntaxKind.ReadonlyKeyword: return parseTypeOperator(operator); case SyntaxKind.InferKeyword: return parseInferType(); } return allowConditionalTypesAnd(parsePostfixTypeOrHigher); } function parseFunctionOrConstructorTypeToError( isInUnionType: boolean ): TypeNode | undefined { // the function type and constructor type shorthand notation // are not allowed directly in unions and intersections, but we'll // try to parse them gracefully and issue a helpful message. if (isStartOfFunctionTypeOrConstructorType()) { const type = parseFunctionOrConstructorType(); let diagnostic: DiagnosticMessage; if (isFunctionTypeNode(type)) { diagnostic = isInUnionType ? Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_a_union_type : Diagnostics.Function_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; } else { diagnostic = isInUnionType ? Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_a_union_type : Diagnostics.Constructor_type_notation_must_be_parenthesized_when_used_in_an_intersection_type; } parseErrorAtRange(type, diagnostic); return type; } return undefined; } function parseUnionOrIntersectionType( operator: SyntaxKind.BarToken | SyntaxKind.AmpersandToken, parseConstituentType: () => TypeNode, createTypeNode: (types: NodeArray) => UnionOrIntersectionTypeNode ): TypeNode { const pos = getNodePos(); const isUnionType = operator === SyntaxKind.BarToken; const hasLeadingOperator = parseOptional(operator); let type = hasLeadingOperator && parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType(); if (token() === operator || hasLeadingOperator) { const types = [type]; while (parseOptional(operator)) { types.push(parseFunctionOrConstructorTypeToError(isUnionType) || parseConstituentType()); } type = finishNode(createTypeNode(createNodeArray(types, pos)), pos); } return type; } function parseIntersectionTypeOrHigher(): TypeNode { return parseUnionOrIntersectionType(SyntaxKind.AmpersandToken, parseTypeOperatorOrHigher, factory.createIntersectionTypeNode); } function parseUnionTypeOrHigher(): TypeNode { return parseUnionOrIntersectionType(SyntaxKind.BarToken, parseIntersectionTypeOrHigher, factory.createUnionTypeNode); } function nextTokenIsNewKeyword(): boolean { nextToken(); return token() === SyntaxKind.NewKeyword; } function isStartOfFunctionTypeOrConstructorType(): boolean { if (token() === SyntaxKind.LessThanToken) { return true; } if (token() === SyntaxKind.OpenParenToken && lookAhead(isUnambiguouslyStartOfFunctionType)) { return true; } return token() === SyntaxKind.NewKeyword || token() === SyntaxKind.AbstractKeyword && lookAhead(nextTokenIsNewKeyword); } function skipParameterStart(): boolean { if (isModifierKind(token())) { // Skip modifiers parseModifiers(); } if (isIdentifier() || token() === SyntaxKind.ThisKeyword) { nextToken(); return true; } if (token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.OpenBraceToken) { // Return true if we can parse an array or object binding pattern with no errors const previousErrorCount = parseDiagnostics.length; parseIdentifierOrPattern(); return previousErrorCount === parseDiagnostics.length; } return false; } function isUnambiguouslyStartOfFunctionType() { nextToken(); if (token() === SyntaxKind.CloseParenToken || token() === SyntaxKind.DotDotDotToken) { // ( ) // ( ... return true; } if (skipParameterStart()) { // We successfully skipped modifiers (if any) and an identifier or binding pattern, // now see if we have something that indicates a parameter declaration if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.QuestionToken || token() === SyntaxKind.EqualsToken) { // ( xxx : // ( xxx , // ( xxx ? // ( xxx = return true; } if (token() === SyntaxKind.CloseParenToken) { nextToken(); if (token() === SyntaxKind.EqualsGreaterThanToken) { // ( xxx ) => return true; } } } return false; } function parseTypeOrTypePredicate(): TypeNode { const pos = getNodePos(); const typePredicateVariable = isIdentifier() && tryParse(parseTypePredicatePrefix); const type = parseType(); if (typePredicateVariable) { return finishNode(factory.createTypePredicateNode(/*assertsModifier*/ undefined, typePredicateVariable, type), pos); } else { return type; } } function parseTypePredicatePrefix() { const id = parseIdentifier(); if (token() === SyntaxKind.IsKeyword && !scanner.hasPrecedingLineBreak()) { nextToken(); return id; } } function parseAssertsTypePredicate(): TypeNode { const pos = getNodePos(); const assertsModifier = parseExpectedToken(SyntaxKind.AssertsKeyword); const parameterName = token() === SyntaxKind.ThisKeyword ? parseThisTypeNode() : parseIdentifier(); const type = parseOptional(SyntaxKind.IsKeyword) ? parseType() : undefined; return finishNode(factory.createTypePredicateNode(assertsModifier, parameterName, type), pos); } function parseType(): TypeNode { if (contextFlags & NodeFlags.TypeExcludesFlags) { return doOutsideOfContext(NodeFlags.TypeExcludesFlags, parseType); } if (isStartOfFunctionTypeOrConstructorType()) { return parseFunctionOrConstructorType(); } const pos = getNodePos(); const type = parseUnionTypeOrHigher(); if (!inDisallowConditionalTypesContext() && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.ExtendsKeyword)) { // The type following 'extends' is not permitted to be another conditional type const extendsType = disallowConditionalTypesAnd(parseType); parseExpected(SyntaxKind.QuestionToken); const trueType = allowConditionalTypesAnd(parseType); parseExpected(SyntaxKind.ColonToken); const falseType = allowConditionalTypesAnd(parseType); return finishNode(factory.createConditionalTypeNode(type, extendsType, trueType, falseType), pos); } return type; } function parseEtsType(pos: number, name: string): TypeNode { const contextFlagsToClear = NodeFlags.TypeExcludesFlags & contextFlags; if (contextFlagsToClear) { // clear the requested context flags setContextFlag(/*val*/ false, contextFlagsToClear); const result = parseEtsTypeReferenceWorker(pos, name); // restore the context flags we just cleared setContextFlag(/*val*/ true, contextFlagsToClear); return result; } return parseEtsTypeReferenceWorker(pos, name); } function parseEtsTypeReferenceWorker(pos: number, name: string): TypeNode { return finishVirtualNode( factory.createTypeReferenceNode( finishVirtualNode(factory.createIdentifier(name), pos, pos) ), pos, pos ); } function parseTypeAnnotation(): TypeNode | undefined { 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.BigIntLiteral: 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; case SyntaxKind.StructKeyword: return inEtsContext(); case SyntaxKind.ImportKeyword: return lookAhead(nextTokenIsOpenParenOrLessThanOrDot); 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.AwaitKeyword: case SyntaxKind.YieldKeyword: case SyntaxKind.PrivateIdentifier: // Yield/await 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 async function, or in strict mode (or both)) and it started a yield or await expression. return true; case SyntaxKind.DotToken: return isValidExtendOrStylesContext(); 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 isValidExtendOrStylesContext(): boolean { return (inEtsExtendComponentsContext() && !!extendEtsComponentDeclaration) || (inEtsStylesComponentsContext() && !!stylesEtsComponentDeclaration); } 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 && (!inEtsContext() || token() !== SyntaxKind.StructKeyword) && 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 const saveDecoratorContext = inDecoratorContext(); if (saveDecoratorContext) { setDecoratorContext(/*val*/ false); } const pos = getNodePos(); let expr = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); let operatorToken: BinaryOperatorToken; while ((operatorToken = parseOptionalToken(SyntaxKind.CommaToken))) { expr = makeBinaryExpression(expr, operatorToken, parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true), pos); } if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); } return expr; } function parseInitializer(): Expression | undefined { return parseOptional(SyntaxKind.EqualsToken) ? parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true) : undefined; } function parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction: boolean): Expression { // AssignmentExpression[in,yield]: // 1) ConditionalExpression[?in,?yield] // 2) LeftHandSideExpression = AssignmentExpression[?in,?yield] // 3) LeftHandSideExpression AssignmentOperator AssignmentExpression[?in,?yield] // 4) ArrowFunctionExpression[?in,?yield] // 5) AsyncArrowFunctionExpression[in,yield,await] // 6) [+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 '6'). if (isYieldExpression()) { return parseYieldExpression(); } // Then, check if we have an arrow function (production '4' and '5') that starts with a parenthesized // parameter list or is an async arrow function. // AsyncArrowFunctionExpression: // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] // Production (1) of AsyncArrowFunctionExpression is parsed in "tryParseAsyncSimpleArrowFunctionExpression". // And production (2) is parsed in "tryParseParenthesizedArrowFunctionExpression". // // If we do successfully parse arrow-function, 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. const arrowExpression = tryParseParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction) || tryParseAsyncSimpleArrowFunctionExpression(allowReturnTypeInArrowFunction); 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. const pos = getNodePos(); const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); // 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(pos, expr as Identifier, allowReturnTypeInArrowFunction, /*asyncModifier*/ undefined); } // 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(allowReturnTypeInArrowFunction), pos); } // It's a CallExpression with open brace followed, therefore, we think it's an EtsComponentExpression if ((inBuildContext() || inBuilderContext()) && inUICallbackContext() && isCallExpression(expr) && token() === SyntaxKind.OpenBraceToken) { return makeEtsComponentExpression(expr, pos); } // It wasn't an assignment or a lambda. This is a conditional expression: return parseConditionalExpressionRest(expr, pos, allowReturnTypeInArrowFunction); } function makeEtsComponentExpression(expression: Expression, pos: number): EtsComponentExpression { const name = (expression).expression; const body = parseFunctionBlock(SignatureFlags.None); return finishNode(factory.createEtsComponentExpression(name, (expression).arguments, body), pos); } function isYieldExpression(): boolean { if (token() === SyntaxKind.YieldKeyword) { // If we have a 'yield' keyword, and this is a context where yield expressions are // allowed, then definitely parse out a yield expression. if (inYieldContext()) { 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 accidentally consume something legal. return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); } return false; } function nextTokenIsIdentifierOnSameLine() { nextToken(); return !scanner.hasPrecedingLineBreak() && isIdentifier(); } function parseYieldExpression(): YieldExpression { const pos = getNodePos(); // 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())) { return finishNode( factory.createYieldExpression( parseOptionalToken(SyntaxKind.AsteriskToken), parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true) ), pos ); } else { // if the next token is not on the same line as yield. or we don't have an '*' or // the start of an expression, then this is just a simple "yield" expression. return finishNode(factory.createYieldExpression(/*asteriskToken*/ undefined, /*expression*/ undefined), pos); } } function parseSimpleArrowFunctionExpression(pos: number, identifier: Identifier, allowReturnTypeInArrowFunction: boolean, asyncModifier?: NodeArray | undefined): ArrowFunction { Debug.assert(token() === SyntaxKind.EqualsGreaterThanToken, "parseSimpleArrowFunctionExpression should only have been called if we had a =>"); const parameter = factory.createParameterDeclaration( /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, identifier, /*questionToken*/ undefined, /*type*/ undefined, /*initializer*/ undefined ); finishNode(parameter, identifier.pos); const parameters = createNodeArray([parameter], parameter.pos, parameter.end); const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); let originUIContextFlag = inUICallbackContext(); let originNoEtsComponentContextFlag = inNoEtsComponentContext(); setUICallbackContext(inSyntaxComponentContext() && !inSyntaxDataSourceContext()); if (inSyntaxComponentContext() && !inSyntaxDataSourceContext()) { setSyntaxComponentContext(false); } setNoEtsComponentContext(!inUICallbackContext()); const body = parseArrowFunctionExpressionBody(/*isAsync*/ !!asyncModifier, allowReturnTypeInArrowFunction); const node = factory.createArrowFunction(asyncModifier, /*typeParameters*/ undefined, parameters, /*type*/ undefined, equalsGreaterThanToken, body); setUICallbackContext(originUIContextFlag); setNoEtsComponentContext(originNoEtsComponentContextFlag); return addJSDocComment(finishNode(node, pos)); } function tryParseParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): Expression | undefined { const 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. return triState === Tristate.True ? parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ true, /*allowReturnTypeInArrowFunction*/ true) : tryParse(() => parsePossibleParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction)); } // 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 || token() === SyntaxKind.AsyncKeyword) { 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() { if (token() === SyntaxKind.AsyncKeyword) { nextToken(); if (scanner.hasPrecedingLineBreak()) { return Tristate.False; } if (token() !== SyntaxKind.OpenParenToken && token() !== SyntaxKind.LessThanToken) { return Tristate.False; } } const first = token(); const 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. const 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; } // Check for "(xxx yyy", where xxx is a modifier and yyy is an identifier. This // isn't actually allowed, but we want to treat it as a lambda so we can provide // a good error message. if (isModifierKind(second) && second !== SyntaxKind.AsyncKeyword && lookAhead(nextTokenIsIdentifier)) { if (nextToken() === SyntaxKind.AsKeyword) { // https://github.com/microsoft/TypeScript/issues/44466 return Tristate.False; } return Tristate.True; } // If we had "(" followed by something that's not an identifier, // then this definitely doesn't look like a lambda. "this" is not // valid, but we want to parse it and then give a semantic error. if (!isIdentifier() && second !== SyntaxKind.ThisKeyword) { return Tristate.False; } switch (nextToken()) { case SyntaxKind.ColonToken: // If we have something like "(a:", then we must have a // type-annotated parameter in an arrow function expression. return Tristate.True; case SyntaxKind.QuestionToken: nextToken(); // If we have "(a?:" or "(a?," or "(a?=" or "(a?)" then it is definitely a lambda. if (token() === SyntaxKind.ColonToken || token() === SyntaxKind.CommaToken || token() === SyntaxKind.EqualsToken || token() === SyntaxKind.CloseParenToken) { return Tristate.True; } // Otherwise it is definitely not a lambda. return Tristate.False; case SyntaxKind.CommaToken: case SyntaxKind.EqualsToken: case SyntaxKind.CloseParenToken: // If we have "(a," or "(a=" or "(a)" this *could* be an arrow function return Tristate.Unknown; } // It is definitely not an arrow function return Tristate.False; } 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; } // JSX overrides if (languageVariant === LanguageVariant.JSX) { const isArrowFunctionInJsx = lookAhead(() => { const third = nextToken(); if (third === SyntaxKind.ExtendsKeyword) { const fourth = nextToken(); switch (fourth) { case SyntaxKind.EqualsToken: case SyntaxKind.GreaterThanToken: return false; default: return true; } } else if (third === SyntaxKind.CommaToken || third === SyntaxKind.EqualsToken) { return true; } return false; }); if (isArrowFunctionInJsx) { return Tristate.True; } return Tristate.False; } // This *could* be a parenthesized arrow function. return Tristate.Unknown; } } function parsePossibleParenthesizedArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { const tokenPos = scanner.getTokenPos(); if (notParenthesizedArrow?.has(tokenPos)) { return undefined; } const result = parseParenthesizedArrowFunctionExpression(/*allowAmbiguity*/ false, allowReturnTypeInArrowFunction); if (!result) { (notParenthesizedArrow || (notParenthesizedArrow = new Set())).add(tokenPos); } return result; } function tryParseAsyncSimpleArrowFunctionExpression(allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { // We do a check here so that we won't be doing unnecessarily call to "lookAhead" if (token() === SyntaxKind.AsyncKeyword) { if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) { const pos = getNodePos(); const asyncModifier = parseModifiersForArrowFunction(); const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); return parseSimpleArrowFunctionExpression(pos, expr as Identifier, allowReturnTypeInArrowFunction, asyncModifier); } } return undefined; } function isUnParenthesizedAsyncArrowFunctionWorker(): Tristate { // AsyncArrowFunctionExpression: // 1) async[no LineTerminator here]AsyncArrowBindingIdentifier[?Yield][no LineTerminator here]=>AsyncConciseBody[?In] // 2) CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await][no LineTerminator here]=>AsyncConciseBody[?In] if (token() === SyntaxKind.AsyncKeyword) { nextToken(); // If the "async" is followed by "=>" token then it is not a beginning of an async arrow-function // but instead a simple arrow-function which will be parsed inside "parseAssignmentExpressionOrHigher" if (scanner.hasPrecedingLineBreak() || token() === SyntaxKind.EqualsGreaterThanToken) { return Tristate.False; } // Check for un-parenthesized AsyncArrowFunction const expr = parseBinaryExpressionOrHigher(OperatorPrecedence.Lowest); if (!scanner.hasPrecedingLineBreak() && expr.kind === SyntaxKind.Identifier && token() === SyntaxKind.EqualsGreaterThanToken) { return Tristate.True; } } return Tristate.False; } function parseParenthesizedArrowFunctionExpression(allowAmbiguity: boolean, allowReturnTypeInArrowFunction: boolean): ArrowFunction | undefined { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const modifiers = parseModifiersForArrowFunction(); const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; // 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. const typeParameters = parseTypeParameters(); let parameters: NodeArray; if (!parseExpected(SyntaxKind.OpenParenToken)) { if (!allowAmbiguity) { return undefined; } parameters = createMissingList(); } else { if (!allowAmbiguity) { const maybeParameters = parseParametersWorker(isAsync, allowAmbiguity); if (!maybeParameters) { return undefined; } parameters = maybeParameters; } else { parameters = parseParametersWorker(isAsync, allowAmbiguity); } if (!parseExpected(SyntaxKind.CloseParenToken) && !allowAmbiguity) { return undefined; } } const hasReturnColon = token() === SyntaxKind.ColonToken; const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); if (type && !allowAmbiguity && typeHasArrowFunctionBlockingParseError(type)) { 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. // - "a ? (b): function() {}" will too, since function() is a valid JSDoc function type. // - "a ? (b): (function() {})" as well, but inside of a parenthesized type with an arbitrary amount of nesting. // // So we need just a bit of lookahead to ensure that it can only be a signature. let unwrappedType = type; while (unwrappedType?.kind === SyntaxKind.ParenthesizedType) { unwrappedType = (unwrappedType as ParenthesizedTypeNode).type; // Skip parens if need be } const hasJSDocFunctionType = unwrappedType && isJSDocFunctionType(unwrappedType); if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && (hasJSDocFunctionType || token() !== SyntaxKind.OpenBraceToken)) { // Returning undefined here will cause our caller to rewind to where we started from. 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. const lastToken = token(); const equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken); let originUIContextFlag = inUICallbackContext(); let originNoEtsComponentContextFlag = inNoEtsComponentContext(); setUICallbackContext(inSyntaxComponentContext() && !inSyntaxDataSourceContext()); if (inSyntaxComponentContext() && !inSyntaxDataSourceContext()) { setSyntaxComponentContext(false); } setNoEtsComponentContext(!inUICallbackContext()); const body = (lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken) ? parseArrowFunctionExpressionBody(some(modifiers, isAsyncModifier), allowReturnTypeInArrowFunction) : parseIdentifier(); setUICallbackContext(originUIContextFlag); setNoEtsComponentContext(originNoEtsComponentContextFlag); // Given: // x ? y => ({ y }) : z => ({ z }) // We try to parse the body of the first arrow function by looking at: // ({ y }) : z => ({ z }) // This is a valid arrow function with "z" as the return type. // // But, if we're in the true side of a conditional expression, this colon // terminates the expression, so we cannot allow a return type if we aren't // certain whether or not the preceding text was parsed as a parameter list. // // For example, // a() ? (b: number, c?: string): void => d() : e // is determined by isParenthesizedArrowFunctionExpression to unambiguously // be an arrow expression, so we allow a return type. if (!allowReturnTypeInArrowFunction && hasReturnColon) { // However, if the arrow function we were able to parse is followed by another colon // as in: // a ? (x): string => x : null // Then allow the arrow function, and treat the second colon as terminating // the conditional expression. It's okay to do this because this code would // be a syntax error in JavaScript (as the second colon shouldn't be there). if (token() !== SyntaxKind.ColonToken) { return undefined; } } const node = factory.createArrowFunction(modifiers, typeParameters, parameters, type, equalsGreaterThanToken, body); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseArrowFunctionExpressionBody(isAsync: boolean, allowReturnTypeInArrowFunction: boolean): Block | Expression { if (token() === SyntaxKind.OpenBraceToken) { return parseFunctionBlock(isAsync ? SignatureFlags.Await : SignatureFlags.None); } if (token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.FunctionKeyword && token() !== SyntaxKind.ClassKeyword && (!inEtsContext() || token() !== SyntaxKind.StructKeyword) && 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, parseBody will still error. return parseFunctionBlock(SignatureFlags.IgnoreMissingOpenBrace | (isAsync ? SignatureFlags.Await : SignatureFlags.None)); } const savedTopLevel = topLevel; topLevel = false; const node = isAsync ? doInAwaitContext(() => parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction)) : doOutsideOfAwaitContext(() => parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction)); topLevel = savedTopLevel; return node; } function parseConditionalExpressionRest(leftOperand: Expression, pos: number, allowReturnTypeInArrowFunction: boolean): Expression { // Note: we are passed in an expression which was produced from parseBinaryExpressionOrHigher. const 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 colonToken; return finishNode( factory.createConditionalExpression( leftOperand, questionToken, doOutsideOfContext(disallowInAndDecoratorContext, () => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ false)), colonToken = parseExpectedToken(SyntaxKind.ColonToken), nodeIsPresent(colonToken) ? parseAssignmentExpressionOrHigher(allowReturnTypeInArrowFunction) : createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics._0_expected, tokenToString(SyntaxKind.ColonToken)) ), pos ); } function parseBinaryExpressionOrHigher(precedence: OperatorPrecedence): Expression { const pos = getNodePos(); const leftOperand = parseUnaryExpressionOrHigher(); return parseBinaryExpressionRest(precedence, leftOperand, pos); } function isInOrOfKeyword(t: SyntaxKind) { return t === SyntaxKind.InKeyword || t === SyntaxKind.OfKeyword; } function parseBinaryExpressionRest(precedence: OperatorPrecedence, leftOperand: Expression, pos: number): 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(); const newPrecedence = getBinaryOperatorPrecedence(token()); // Check the precedence to see if we should "take" this operator // - For left associative operator (all operator but **), consume the operator, // recursively call the function below, and parse binaryExpression as a rightOperand // of the caller if the new precedence of the operator is greater then or equal to the current precedence. // For example: // a - b - c; // ^token; leftOperand = b. Return b to the caller as a rightOperand // a * b - c // ^token; leftOperand = b. Return b to the caller as a rightOperand // a - b * c; // ^token; leftOperand = b. Return b * c to the caller as a rightOperand // - For right associative operator (**), consume the operator, recursively call the function // and parse binaryExpression as a rightOperand of the caller if the new precedence of // the operator is strictly grater than the current precedence // For example: // a ** b ** c; // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand // a - b ** c; // ^^token; leftOperand = b. Return b ** c to the caller as a rightOperand // a ** b - c // ^token; leftOperand = b. Return b to the caller as a rightOperand const consumeCurrentOperator = token() === SyntaxKind.AsteriskAsteriskToken ? newPrecedence >= precedence : newPrecedence > precedence; if (!consumeCurrentOperator) { break; } if (token() === SyntaxKind.InKeyword && inDisallowInContext()) { break; } if (token() === SyntaxKind.AsKeyword || token() === SyntaxKind.SatisfiesKeyword) { // Make sure we *do* perform ASI for constructs like this: // var x = foo // as (Bar) // This should be parsed as an initialized variable, followed // by a function call to 'as' with the argument 'Bar' if (scanner.hasPrecedingLineBreak()) { break; } else { const keywordKind = token(); nextToken(); leftOperand = keywordKind === SyntaxKind.SatisfiesKeyword ? makeSatisfiesExpression(leftOperand, parseType()) : makeAsExpression(leftOperand, parseType()); } } else { leftOperand = makeBinaryExpression(leftOperand, parseTokenNode(), parseBinaryExpressionOrHigher(newPrecedence), pos); } } return leftOperand; } function isBinaryOperator() { if (inDisallowInContext() && token() === SyntaxKind.InKeyword) { return false; } return getBinaryOperatorPrecedence(token()) > 0; } function makeSatisfiesExpression(left: Expression, right: TypeNode): SatisfiesExpression { return finishNode(factory.createSatisfiesExpression(left, right), left.pos); } function makeBinaryExpression(left: Expression, operatorToken: BinaryOperatorToken, right: Expression, pos: number): BinaryExpression { return finishNode(factory.createBinaryExpression(left, operatorToken, right), pos); } function makeAsExpression(left: Expression, right: TypeNode): AsExpression { return finishNode(factory.createAsExpression(left, right), left.pos); } function parsePrefixUnaryExpression() { const pos = getNodePos(); return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseSimpleUnaryExpression)), pos); } function parseDeleteExpression() { const pos = getNodePos(); return finishNode(factory.createDeleteExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); } function parseTypeOfExpression() { const pos = getNodePos(); return finishNode(factory.createTypeOfExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); } function parseVoidExpression() { const pos = getNodePos(); return finishNode(factory.createVoidExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); } function isAwaitExpression(): boolean { if (token() === SyntaxKind.AwaitKeyword) { if (inAwaitContext()) { return true; } // here we are using similar heuristics as 'isYieldExpression' return lookAhead(nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine); } return false; } function parseAwaitExpression() { const pos = getNodePos(); return finishNode(factory.createAwaitExpression(nextTokenAnd(parseSimpleUnaryExpression)), pos); } /** * Parse ES7 exponential expression and await expression * * ES7 ExponentiationExpression: * 1) UnaryExpression[?Yield] * 2) UpdateExpression[?Yield] ** ExponentiationExpression[?Yield] * */ function parseUnaryExpressionOrHigher(): UnaryExpression | BinaryExpression { /** * ES7 UpdateExpression: * 1) LeftHandSideExpression[?Yield] * 2) LeftHandSideExpression[?Yield][no LineTerminator here]++ * 3) LeftHandSideExpression[?Yield][no LineTerminator here]-- * 4) ++UnaryExpression[?Yield] * 5) --UnaryExpression[?Yield] */ if (isUpdateExpression()) { const pos = getNodePos(); const updateExpression = parseUpdateExpression(); return token() === SyntaxKind.AsteriskAsteriskToken ? parseBinaryExpressionRest(getBinaryOperatorPrecedence(token()), updateExpression, pos) as BinaryExpression : updateExpression; } /** * ES7 UnaryExpression: * 1) UpdateExpression[?yield] * 2) delete UpdateExpression[?yield] * 3) void UpdateExpression[?yield] * 4) typeof UpdateExpression[?yield] * 5) + UpdateExpression[?yield] * 6) - UpdateExpression[?yield] * 7) ~ UpdateExpression[?yield] * 8) ! UpdateExpression[?yield] */ const unaryOperator = token(); const simpleUnaryExpression = parseSimpleUnaryExpression(); if (token() === SyntaxKind.AsteriskAsteriskToken) { const pos = skipTrivia(sourceText, simpleUnaryExpression.pos); const { end } = simpleUnaryExpression; if (simpleUnaryExpression.kind === SyntaxKind.TypeAssertionExpression) { parseErrorAt(pos, end, Diagnostics.A_type_assertion_expression_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses); } else { parseErrorAt(pos, end, Diagnostics.An_unary_expression_with_the_0_operator_is_not_allowed_in_the_left_hand_side_of_an_exponentiation_expression_Consider_enclosing_the_expression_in_parentheses, tokenToString(unaryOperator)); } } return simpleUnaryExpression; } /** * Parse ES7 simple-unary expression or higher: * * ES7 UnaryExpression: * 1) UpdateExpression[?yield] * 2) delete UnaryExpression[?yield] * 3) void UnaryExpression[?yield] * 4) typeof UnaryExpression[?yield] * 5) + UnaryExpression[?yield] * 6) - UnaryExpression[?yield] * 7) ~ UnaryExpression[?yield] * 8) ! UnaryExpression[?yield] * 9) [+Await] await UnaryExpression[?yield] */ function parseSimpleUnaryExpression(): UnaryExpression { switch (token()) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: case SyntaxKind.ExclamationToken: return parsePrefixUnaryExpression(); case SyntaxKind.DeleteKeyword: return parseDeleteExpression(); case SyntaxKind.TypeOfKeyword: return parseTypeOfExpression(); case SyntaxKind.VoidKeyword: return parseVoidExpression(); case SyntaxKind.LessThanToken: // This is modified UnaryExpression grammar in TypeScript // UnaryExpression (modified): // < type > UnaryExpression return parseTypeAssertion(); case SyntaxKind.AwaitKeyword: if (isAwaitExpression()) { return parseAwaitExpression(); } // falls through default: return parseUpdateExpression(); } } /** * Check if the current token can possibly be an ES7 increment expression. * * ES7 UpdateExpression: * LeftHandSideExpression[?Yield] * LeftHandSideExpression[?Yield][no LineTerminator here]++ * LeftHandSideExpression[?Yield][no LineTerminator here]-- * ++LeftHandSideExpression[?Yield] * --LeftHandSideExpression[?Yield] */ function isUpdateExpression(): boolean { // This function is called inside parseUnaryExpression to decide // whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly 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.AwaitKeyword: return false; case SyntaxKind.LessThanToken: // If we are not in JSX context, we are parsing TypeAssertion which is an UnaryExpression if (languageVariant !== LanguageVariant.JSX) { return false; } // We are in JSX context and the token is part of JSXElement. // falls through default: return true; } } /** * Parse ES7 UpdateExpression. UpdateExpression is used instead of ES6's PostFixExpression. * * ES7 UpdateExpression[yield]: * 1) LeftHandSideExpression[?yield] * 2) LeftHandSideExpression[?yield] [[no LineTerminator here]]++ * 3) LeftHandSideExpression[?yield] [[no LineTerminator here]]-- * 4) ++LeftHandSideExpression[?yield] * 5) --LeftHandSideExpression[?yield] * In TypeScript (2), (3) are parsed as PostfixUnaryExpression. (4), (5) are parsed as PrefixUnaryExpression */ function parseUpdateExpression(): UpdateExpression { if (token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) { const pos = getNodePos(); return finishNode(factory.createPrefixUnaryExpression(token() as PrefixUnaryOperator, nextTokenAnd(parseLeftHandSideExpressionOrHigher)), pos); } else if (languageVariant === LanguageVariant.JSX && token() === SyntaxKind.LessThanToken && lookAhead(nextTokenIsIdentifierOrKeywordOrGreaterThan)) { // JSXElement is part of primaryExpression return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); } const expression = parseLeftHandSideExpressionOrHigher(); Debug.assert(isLeftHandSideExpression(expression)); if ((token() === SyntaxKind.PlusPlusToken || token() === SyntaxKind.MinusMinusToken) && !scanner.hasPrecedingLineBreak()) { const operator = token() as PostfixUnaryOperator; nextToken(); return finishNode(factory.createPostfixUnaryExpression(expression, operator), expression.pos); } 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 // import (AssignmentExpression) // super Arguments // super.IdentifierName // // Because of the recursion in these calls, we need to bottom out first. There are three // bottom out states we can run into: 1) We see 'super' which must start either of // the last two CallExpression productions. 2) We see 'import' which must start import call. // 3)we have a MemberExpression which either completes the LeftHandSideExpression, // or starts the beginning of the first four CallExpression productions. const pos = getNodePos(); let expression: MemberExpression; if (token() === SyntaxKind.ImportKeyword) { if (lookAhead(nextTokenIsOpenParenOrLessThan)) { // We don't want to eagerly consume all import keyword as import call expression so we look ahead to find "(" // For example: // var foo3 = require("subfolder // import * as foo1 from "module-from-node // We want this import to be a statement rather than import call expression sourceFlags |= NodeFlags.PossiblyContainsDynamicImport; expression = parseTokenNode(); } else if (lookAhead(nextTokenIsDot)) { // This is an 'import.*' metaproperty (i.e. 'import.meta') nextToken(); // advance past the 'import' nextToken(); // advance past the dot expression = finishNode(factory.createMetaProperty(SyntaxKind.ImportKeyword, parseIdentifierName()), pos); sourceFlags |= NodeFlags.PossiblyContainsImportMeta; } else { expression = parseMemberExpressionOrHigher(); } } else { expression = token() === SyntaxKind.SuperKeyword ? parseSuperExpression() : parseMemberExpressionOrHigher(); } // Now, we *may* be complete. However, we might have consumed the start of a // CallExpression or OptionalExpression. As such, we need to consume the rest // of it here to be complete. return parseCallExpressionRest(pos, expression); } function parseMemberExpressionOrHigher(): MemberExpression { // Note: to make our lives simpler, we decompose 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. const pos = getNodePos(); let expression; if (inEtsExtendComponentsContext() && extendEtsComponentDeclaration && token() === SyntaxKind.DotToken) { expression = finishVirtualNode(factory.createIdentifier(extendEtsComponentDeclaration.instance, /*typeArguments*/ undefined, SyntaxKind.Identifier), pos, pos); } else if (inEtsStylesComponentsContext() && stylesEtsComponentDeclaration && token() === SyntaxKind.DotToken) { expression = finishVirtualNode(factory.createIdentifier(stylesEtsComponentDeclaration.instance, /*typeArguments*/ undefined, SyntaxKind.Identifier), pos, pos); } else if (inEtsStateStylesContext() && stateStylesRootNode && token() === SyntaxKind.DotToken) { expression = finishVirtualNode(factory.createIdentifier(`${stateStylesRootNode}Instance`, /*typeArguments*/ undefined, SyntaxKind.Identifier), pos, pos); } else { expression = parsePrimaryExpression(); } return parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); } function parseSuperExpression(): MemberExpression { const pos = getNodePos(); let expression = parseTokenNode(); if (token() === SyntaxKind.LessThanToken) { const startPos = getNodePos(); const typeArguments = tryParse(parseTypeArgumentsInExpression); if (typeArguments !== undefined) { parseErrorAt(startPos, getNodePos(), Diagnostics.super_may_not_use_type_arguments); if (!isTemplateStartOfTaggedTemplate()) { expression = factory.createExpressionWithTypeArguments(expression, typeArguments); } } } if (token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.DotToken || token() === SyntaxKind.OpenBracketToken) { 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. parseExpectedToken(SyntaxKind.DotToken, Diagnostics.super_must_be_followed_by_an_argument_list_or_member_access); // private names will never work with `super` (`super.#foo`), but that's a semantic error, not syntactic return finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true)), pos); } function parseJsxElementOrSelfClosingElementOrFragment(inExpressionContext: boolean, topInvalidNodePosition?: number, openingTag?: JsxOpeningElement | JsxOpeningFragment): JsxElement | JsxSelfClosingElement | JsxFragment { const pos = getNodePos(); const opening = parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext); let result: JsxElement | JsxSelfClosingElement | JsxFragment; if (opening.kind === SyntaxKind.JsxOpeningElement) { let children = parseJsxChildren(opening); let closingElement: JsxClosingElement; const lastChild: JsxChild | undefined = children[children.length - 1]; if (lastChild?.kind === SyntaxKind.JsxElement && !tagNamesAreEquivalent(lastChild.openingElement.tagName, lastChild.closingElement.tagName) && tagNamesAreEquivalent(opening.tagName, lastChild.closingElement.tagName)) { // when an unclosed JsxOpeningElement incorrectly parses its parent's JsxClosingElement, // restructure (
(......
)) --> (
(......)
) // (no need to error; the parent will error) const end = lastChild.children.end; const newLast = finishNode(factory.createJsxElement( lastChild.openingElement, lastChild.children, finishNode(factory.createJsxClosingElement(finishNode(factory.createIdentifier(""), end, end)), end, end)), lastChild.openingElement.pos, end); children = createNodeArray([...children.slice(0, children.length - 1), newLast], children.pos, end); closingElement = lastChild.closingElement; } else { closingElement = parseJsxClosingElement(opening, inExpressionContext); if (!tagNamesAreEquivalent(opening.tagName, closingElement.tagName)) { if (openingTag && isJsxOpeningElement(openingTag) && tagNamesAreEquivalent(closingElement.tagName, openingTag.tagName)) { // opening incorrectly matched with its parent's closing -- put error on opening parseErrorAtRange(opening.tagName, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, opening.tagName)); } else { // other opening/closing mismatches -- put error on closing parseErrorAtRange(closingElement.tagName, Diagnostics.Expected_corresponding_JSX_closing_tag_for_0, getTextOfNodeFromSourceText(sourceText, opening.tagName)); } } } result = finishNode(factory.createJsxElement(opening, children, closingElement), pos); } else if (opening.kind === SyntaxKind.JsxOpeningFragment) { result = finishNode(factory.createJsxFragment(opening, parseJsxChildren(opening), parseJsxClosingFragment(inExpressionContext)), pos); } else { Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement); // Nothing else to do for self-closing elements result = opening; } // If the user writes the invalid code '
' in an expression context (i.e. not wrapped in // an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag // as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX // element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter // does less damage and we can report a better error. // Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios // of one sort or another. if (inExpressionContext && token() === SyntaxKind.LessThanToken) { const topBadPos = typeof topInvalidNodePosition === "undefined" ? result.pos : topInvalidNodePosition; const invalidElement = tryParse(() => parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true, topBadPos)); if (invalidElement) { const operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false); setTextRangePosWidth(operatorToken, invalidElement.pos, 0); parseErrorAt(skipTrivia(sourceText, topBadPos), invalidElement.end, Diagnostics.JSX_expressions_must_have_one_parent_element); return finishNode(factory.createBinaryExpression(result, operatorToken as Token, invalidElement), pos) as Node as JsxElement; } } return result; } function parseJsxText(): JsxText { const pos = getNodePos(); const node = factory.createJsxText(scanner.getTokenValue(), currentToken === SyntaxKind.JsxTextAllWhiteSpaces); currentToken = scanner.scanJsxToken(); return finishNode(node, pos); } function parseJsxChild(openingTag: JsxOpeningElement | JsxOpeningFragment, token: JsxTokenSyntaxKind): JsxChild | undefined { switch (token) { case SyntaxKind.EndOfFileToken: // If we hit EOF, issue the error at the tag that lacks the closing element // rather than at the end of the file (which is useless) if (isJsxOpeningFragment(openingTag)) { parseErrorAtRange(openingTag, Diagnostics.JSX_fragment_has_no_corresponding_closing_tag); } else { // We want the error span to cover only 'Foo.Bar' in < Foo.Bar > // or to cover only 'Foo' in < Foo > const tag = openingTag.tagName; const start = skipTrivia(sourceText, tag.pos); parseErrorAt(start, tag.end, Diagnostics.JSX_element_0_has_no_corresponding_closing_tag, getTextOfNodeFromSourceText(sourceText, openingTag.tagName)); } return undefined; case SyntaxKind.LessThanSlashToken: case SyntaxKind.ConflictMarkerTrivia: return undefined; case SyntaxKind.JsxText: case SyntaxKind.JsxTextAllWhiteSpaces: return parseJsxText(); case SyntaxKind.OpenBraceToken: return parseJsxExpression(/*inExpressionContext*/ false); case SyntaxKind.LessThanToken: return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ false, /*topInvalidNodePosition*/ undefined, openingTag); default: return Debug.assertNever(token); } } function parseJsxChildren(openingTag: JsxOpeningElement | JsxOpeningFragment): NodeArray { const list = []; const listPos = getNodePos(); const saveParsingContext = parsingContext; parsingContext |= 1 << ParsingContext.JsxChildren; while (true) { const child = parseJsxChild(openingTag, currentToken = scanner.reScanJsxToken()); if (!child) break; list.push(child); if (isJsxOpeningElement(openingTag) && child?.kind === SyntaxKind.JsxElement && !tagNamesAreEquivalent(child.openingElement.tagName, child.closingElement.tagName) && tagNamesAreEquivalent(openingTag.tagName, child.closingElement.tagName)) { // stop after parsing a mismatched child like
...(
) in order to reattach the higher break; } } parsingContext = saveParsingContext; return createNodeArray(list, listPos); } function parseJsxAttributes(): JsxAttributes { const pos = getNodePos(); return finishNode(factory.createJsxAttributes(parseList(ParsingContext.JsxAttributes, parseJsxAttribute)), pos); } function parseJsxOpeningOrSelfClosingElementOrOpeningFragment(inExpressionContext: boolean): JsxOpeningElement | JsxSelfClosingElement | JsxOpeningFragment { const pos = getNodePos(); parseExpected(SyntaxKind.LessThanToken); if (token() === SyntaxKind.GreaterThanToken) { // See below for explanation of scanJsxText scanJsxText(); return finishNode(factory.createJsxOpeningFragment(), pos); } const tagName = parseJsxElementName(); const typeArguments = (contextFlags & NodeFlags.JavaScriptFile) === 0 ? tryParseTypeArguments() : undefined; const attributes = parseJsxAttributes(); let node: JsxOpeningLikeElement; if (token() === SyntaxKind.GreaterThanToken) { // Closing tag, so scan the immediately-following text with the JSX scanning instead // of regular scanning to avoid treating illegal characters (e.g. '#') as immediate // scanning errors scanJsxText(); node = factory.createJsxOpeningElement(tagName, typeArguments, attributes); } else { parseExpected(SyntaxKind.SlashToken); if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { // manually advance the scanner in order to look for jsx text inside jsx if (inExpressionContext) { nextToken(); } else { scanJsxText(); } } node = factory.createJsxSelfClosingElement(tagName, typeArguments, attributes); } return finishNode(node, pos); } function parseJsxElementName(): JsxTagNameExpression { const pos = getNodePos(); scanJsxIdentifier(); // JsxElement can have name in the form of // propertyAccessExpression // primaryExpression in the form of an identifier and "this" keyword // We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword // We only want to consider "this" as a primaryExpression let expression: JsxTagNameExpression = token() === SyntaxKind.ThisKeyword ? parseTokenNode() : parseIdentifierName(); while (parseOptional(SyntaxKind.DotToken)) { expression = finishNode(factory.createPropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess; } return expression; } function parseJsxExpression(inExpressionContext: boolean): JsxExpression | undefined { const pos = getNodePos(); if (!parseExpected(SyntaxKind.OpenBraceToken)) { return undefined; } let dotDotDotToken: DotDotDotToken | undefined; let expression: Expression | undefined; if (token() !== SyntaxKind.CloseBraceToken) { dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); // Only an AssignmentExpression is valid here per the JSX spec, // but we can unambiguously parse a comma sequence and provide // a better error message in grammar checking. expression = parseExpression(); } if (inExpressionContext) { parseExpected(SyntaxKind.CloseBraceToken); } else { if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) { scanJsxText(); } } return finishNode(factory.createJsxExpression(dotDotDotToken, expression), pos); } function parseJsxAttribute(): JsxAttribute | JsxSpreadAttribute { if (token() === SyntaxKind.OpenBraceToken) { return parseJsxSpreadAttribute(); } scanJsxIdentifier(); const pos = getNodePos(); return finishNode(factory.createJsxAttribute(parseIdentifierName(), parseJsxAttributeValue()), pos); } function parseJsxAttributeValue(): JsxAttributeValue | undefined { if (token() === SyntaxKind.EqualsToken) { if (scanJsxAttributeValue() === SyntaxKind.StringLiteral) { return parseLiteralNode() as StringLiteral; } if (token() === SyntaxKind.OpenBraceToken) { return parseJsxExpression(/*inExpressionContext*/ true); } if (token() === SyntaxKind.LessThanToken) { return parseJsxElementOrSelfClosingElementOrFragment(/*inExpressionContext*/ true); } parseErrorAtCurrentToken(Diagnostics.or_JSX_element_expected); } return undefined; } function parseJsxSpreadAttribute(): JsxSpreadAttribute { const pos = getNodePos(); parseExpected(SyntaxKind.OpenBraceToken); parseExpected(SyntaxKind.DotDotDotToken); const expression = parseExpression(); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(factory.createJsxSpreadAttribute(expression), pos); } function parseJsxClosingElement(open: JsxOpeningElement, inExpressionContext: boolean): JsxClosingElement { const pos = getNodePos(); parseExpected(SyntaxKind.LessThanSlashToken); const tagName = parseJsxElementName(); if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { // manually advance the scanner in order to look for jsx text inside jsx if (inExpressionContext || !tagNamesAreEquivalent(open.tagName, tagName)) { nextToken(); } else { scanJsxText(); } } return finishNode(factory.createJsxClosingElement(tagName), pos); } function parseJsxClosingFragment(inExpressionContext: boolean): JsxClosingFragment { const pos = getNodePos(); parseExpected(SyntaxKind.LessThanSlashToken); if (tokenIsIdentifierOrKeyword(token())) { parseErrorAtRange(parseJsxElementName(), Diagnostics.Expected_corresponding_closing_tag_for_JSX_fragment); } if (parseExpected(SyntaxKind.GreaterThanToken, /*diagnostic*/ undefined, /*shouldAdvance*/ false)) { // manually advance the scanner in order to look for jsx text inside jsx if (inExpressionContext) { nextToken(); } else { scanJsxText(); } } return finishNode(factory.createJsxJsxClosingFragment(), pos); } function parseTypeAssertion(): TypeAssertion { const pos = getNodePos(); parseExpected(SyntaxKind.LessThanToken); const type = parseType(); parseExpected(SyntaxKind.GreaterThanToken); const expression = parseSimpleUnaryExpression(); return finishNode(factory.createTypeAssertion(type, expression), pos); } function nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate() { nextToken(); return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBracketToken || isTemplateStartOfTaggedTemplate(); } function isStartOfOptionalPropertyOrElementAccessChain() { return token() === SyntaxKind.QuestionDotToken && lookAhead(nextTokenIsIdentifierOrKeywordOrOpenBracketOrTemplate); } function tryReparseOptionalChain(node: Expression) { if (node.flags & NodeFlags.OptionalChain) { return true; } // check for an optional chain in a non-null expression if (isNonNullExpression(node)) { let expr = node.expression; while (isNonNullExpression(expr) && !(expr.flags & NodeFlags.OptionalChain)) { expr = expr.expression; } if (expr.flags & NodeFlags.OptionalChain) { // this is part of an optional chain. Walk down from `node` to `expression` and set the flag. while (isNonNullExpression(node)) { (node as Mutable).flags |= NodeFlags.OptionalChain; node = node.expression; } return true; } } return false; } function parsePropertyAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { const name = parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ true); const isOptionalChain = questionDotToken || tryReparseOptionalChain(expression); const propertyAccess = isOptionalChain ? factory.createPropertyAccessChain(expression, questionDotToken, name) : factory.createPropertyAccessExpression(expression, name); if (isOptionalChain && isPrivateIdentifier(propertyAccess.name)) { parseErrorAtRange(propertyAccess.name, Diagnostics.An_optional_chain_cannot_contain_private_identifiers); } if (isExpressionWithTypeArguments(expression) && expression.typeArguments) { const pos = expression.typeArguments.pos - 1; const end = skipTrivia(sourceText, expression.typeArguments.end) + 1; parseErrorAt(pos, end, Diagnostics.An_instantiation_expression_cannot_be_followed_by_a_property_access); } return finishNode(propertyAccess, pos); } function parseElementAccessExpressionRest(pos: number, expression: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined) { let argumentExpression: Expression; if (token() === SyntaxKind.CloseBracketToken) { argumentExpression = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.An_element_access_expression_should_take_an_argument); } else { const argument = allowInAnd(parseExpression); if (isStringOrNumericLiteralLike(argument)) { argument.text = internIdentifier(argument.text); } argumentExpression = argument; } parseExpected(SyntaxKind.CloseBracketToken); const indexedAccess = questionDotToken || tryReparseOptionalChain(expression) ? factory.createElementAccessChain(expression, questionDotToken, argumentExpression) : factory.createElementAccessExpression(expression, argumentExpression); return finishNode(indexedAccess, pos); } function parseMemberExpressionRest(pos: number, expression: LeftHandSideExpression, allowOptionalChain: boolean): MemberExpression { while (true) { let questionDotToken: QuestionDotToken | undefined; let isPropertyAccess = false; if (allowOptionalChain && isStartOfOptionalPropertyOrElementAccessChain()) { questionDotToken = parseExpectedToken(SyntaxKind.QuestionDotToken); isPropertyAccess = tokenIsIdentifierOrKeyword(token()); } else { isPropertyAccess = parseOptional(SyntaxKind.DotToken); } if (isPropertyAccess) { expression = parsePropertyAccessExpressionRest(pos, expression, questionDotToken); continue; } // when in the [Decorator] context, we do not parse ElementAccess as it could be part of a ComputedPropertyName if ((questionDotToken || !inDecoratorContext()) && parseOptional(SyntaxKind.OpenBracketToken)) { expression = parseElementAccessExpressionRest(pos, expression, questionDotToken); continue; } if (isTemplateStartOfTaggedTemplate()) { // Absorb type arguments into TemplateExpression when preceding expression is ExpressionWithTypeArguments expression = !questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments ? parseTaggedTemplateRest(pos, (expression as ExpressionWithTypeArguments).expression, questionDotToken, (expression as ExpressionWithTypeArguments).typeArguments) : parseTaggedTemplateRest(pos, expression, questionDotToken, /*typeArguments*/ undefined); continue; } if (!questionDotToken) { if (token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { nextToken(); expression = finishNode(factory.createNonNullExpression(expression), pos); continue; } const typeArguments = tryParse(parseTypeArgumentsInExpression); if (typeArguments) { expression = finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); continue; } } return expression as MemberExpression; } } function isTemplateStartOfTaggedTemplate() { return token() === SyntaxKind.NoSubstitutionTemplateLiteral || token() === SyntaxKind.TemplateHead; } function parseTaggedTemplateRest(pos: number, tag: LeftHandSideExpression, questionDotToken: QuestionDotToken | undefined, typeArguments: NodeArray | undefined) { const tagExpression = factory.createTaggedTemplateExpression( tag, typeArguments, token() === SyntaxKind.NoSubstitutionTemplateLiteral ? (reScanTemplateHeadOrNoSubstitutionTemplate(), parseLiteralNode() as NoSubstitutionTemplateLiteral) : parseTemplateExpression(/*isTaggedTemplate*/ true) ); if (questionDotToken || tag.flags & NodeFlags.OptionalChain) { (tagExpression as Mutable).flags |= NodeFlags.OptionalChain; } tagExpression.questionDotToken = questionDotToken; return finishNode(tagExpression, pos); } function parseCallExpressionRest(pos: number, expression: LeftHandSideExpression): LeftHandSideExpression { let currentNodeName: string | undefined; while (true) { expression = parseMemberExpressionRest(pos, expression, /*allowOptionalChain*/ true); let typeArguments: NodeArray | undefined; const questionDotToken = parseOptionalToken(SyntaxKind.QuestionDotToken); if (questionDotToken) { typeArguments = tryParse(parseTypeArgumentsInExpression); if (isTemplateStartOfTaggedTemplate()) { expression = parseTaggedTemplateRest(pos, expression, questionDotToken, typeArguments); continue; } } if (typeArguments || token() === SyntaxKind.OpenParenToken) { // Absorb type arguments into CallExpression when preceding expression is ExpressionWithTypeArguments if (!questionDotToken && expression.kind === SyntaxKind.ExpressionWithTypeArguments) { typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; expression = (expression as ExpressionWithTypeArguments).expression; } if (isValidVirtualTypeArgumentsContext() && isPropertyAccessExpression(expression)) { const [rootNode, type] = getRootComponent(expression, sourceFileCompilerOptions); if (rootNode && type) { let rootNodeName = ''; if (type === 'otherType') { rootNodeName = 'Common'; } else { rootNodeName = ((rootNode.expression)).escapedText.toString(); } currentNodeName = getTextOfPropertyName(expression.name).toString(); if (currentNodeName === sourceFileCompilerOptions?.ets?.styles?.property) { setEtsStateStylesContext(true); stateStylesRootNode = rootNodeName; } else { setEtsStateStylesContext(false); stateStylesRootNode = undefined; } const syntaxComponents = sourceFileCompilerOptions?.ets?.syntaxComponents?.attrUICallback?.filter( (item: any) => item.name === rootNodeName); if (type === 'callExpressionComponentType' && syntaxComponents && syntaxComponents.length && syntaxComponents[0]?.attributes?.includes(currentNodeName)) { setSyntaxComponentContext(true); setFirstArgumentExpression(true); if (currentNodeName === 'each') { setRepeatEachRest(true); } } else if (type === 'etsComponentType') { typeArguments = parseEtsTypeArguments(pos, `${rootNodeName}Attribute`); } } else if (inEtsStateStylesContext() && stateStylesRootNode) { typeArguments = parseEtsTypeArguments(pos, `${stateStylesRootNode}Attribute`); } else if (inEtsStylesComponentsContext() || inEtsExtendComponentsContext()) { const virtualNode = getVirtualEtsComponent(expression); if (virtualNode) { let rootNodeName = ((virtualNode.expression)).escapedText.toString(); currentNodeName = getTextOfPropertyName(expression.name).toString(); if (currentNodeName === sourceFileCompilerOptions?.ets?.styles?.property) { setEtsStateStylesContext(true); rootNodeName = rootNodeName.replace("Instance", ""); stateStylesRootNode = rootNodeName; typeArguments = parseEtsTypeArguments(pos, `${rootNodeName}Attribute`); } } } } if (isValidVirtualTypeArgumentsContext() && ts.isIdentifier(expression) && sourceFileCompilerOptions?.ets?.syntaxComponents?.paramsUICallback?.includes(expression.escapedText.toString())) { setSyntaxComponentContext(true); setFirstArgumentExpression(true); } const argumentList = parseArgumentList(); const callExpr = questionDotToken || tryReparseOptionalChain(expression) ? factory.createCallChain(expression, questionDotToken, typeArguments, argumentList) : factory.createCallExpression(expression, typeArguments, argumentList); expression = finishNode(callExpr, pos); continue; } if (questionDotToken) { // We parsed `?.` but then failed to parse anything, so report a missing identifier here. const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ false, Diagnostics.Identifier_expected); expression = finishNode(factory.createPropertyAccessChain(expression, questionDotToken, name), pos); } break; } if (currentNodeName === sourceFileCompilerOptions?.ets?.styles?.property) { setEtsStateStylesContext(false); stateStylesRootNode = undefined; } return expression; } function isValidVirtualTypeArgumentsContext(): boolean { return inBuildContext() || inBuilderContext() || inEtsStylesComponentsContext() || inEtsExtendComponentsContext(); } function parseArgumentList() { parseExpected(SyntaxKind.OpenParenToken); const result = parseDelimitedList(ParsingContext.ArgumentExpressions, parseArgumentExpression); parseExpected(SyntaxKind.CloseParenToken); return result; } function parseTypeArgumentsInExpression() { if ((contextFlags & NodeFlags.JavaScriptFile) !== 0) { // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. return undefined; } if (reScanLessThanToken() !== SyntaxKind.LessThanToken) { return undefined; } nextToken(); const typeArguments = parseDelimitedList(ParsingContext.TypeArguments, parseType); if (reScanGreaterToken() !== SyntaxKind.GreaterThanToken) { // If it doesn't have the closing `>` then it's definitely not an type argument list. return undefined; } nextToken(); // We successfully parsed a type argument list. The next token determines whether we want to // treat it as such. If the type argument list is followed by `(` or a template literal, as in // `f(42)`, we favor the type argument interpretation even though JavaScript would view // it as a relational expression. return typeArguments && canFollowTypeArgumentsInExpression() ? typeArguments : undefined; } function canFollowTypeArgumentsInExpression(): boolean { switch (token()) { // These tokens can follow a type argument list in a call expression. case SyntaxKind.OpenParenToken: // foo( case SyntaxKind.NoSubstitutionTemplateLiteral: // foo `...` case SyntaxKind.TemplateHead: // foo `...${100}...` return true; // A type argument list followed by `<` never makes sense, and a type argument list followed // by `>` is ambiguous with a (re-scanned) `>>` operator, so we disqualify both. Also, in // this context, `+` and `-` are unary operators, not binary operators. case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: return false; } // We favor the type argument list interpretation when it is immediately followed by // a line break, a binary operator, or something that can't start an expression. return scanner.hasPrecedingLineBreak() || isBinaryOperator() || !isStartOfExpression(); } function isCurrentTokenAnEtsComponentExpression(): boolean { if (!inEtsComponentsContext() || inNoEtsComponentContext()) { return false; } const components = sourceFileCompilerOptions.ets?.components ?? []; return components.includes(scanner.getTokenText()); } function parseEtsComponentExpression(): EtsComponentExpression { const pos = getNodePos(); const name = parseBindingIdentifier(); const argumentList = parseArgumentList(); const body = token() === SyntaxKind.OpenBraceToken ? parseFunctionBlock(SignatureFlags.None) : undefined; const node = factory.createEtsComponentExpression(name, argumentList, body); return finishNode(node, pos); } function parsePrimaryExpression(): PrimaryExpression { switch (token()) { case SyntaxKind.NumericLiteral: case SyntaxKind.BigIntLiteral: 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(); case SyntaxKind.OpenParenToken: return parseParenthesizedExpression(); case SyntaxKind.OpenBracketToken: return parseArrayLiteralExpression(); case SyntaxKind.OpenBraceToken: return parseObjectLiteralExpression(); case SyntaxKind.AsyncKeyword: // Async arrow functions are parsed earlier in parseAssignmentExpressionOrHigher. // If we encounter `async [no LineTerminator here] function` then this is an async // function; otherwise, its an identifier. if (!lookAhead(nextTokenIsFunctionKeywordOnSameLine)) { break; } return parseFunctionExpression(); case SyntaxKind.ClassKeyword: return parseClassExpression(); case SyntaxKind.FunctionKeyword: return parseFunctionExpression(); case SyntaxKind.NewKeyword: return parseNewExpressionOrNewDotTarget(); case SyntaxKind.SlashToken: case SyntaxKind.SlashEqualsToken: if (reScanSlashToken() === SyntaxKind.RegularExpressionLiteral) { return parseLiteralNode(); } break; case SyntaxKind.TemplateHead: return parseTemplateExpression(/* isTaggedTemplate */ false); case SyntaxKind.PrivateIdentifier: return parsePrivateIdentifier(); } if(isCurrentTokenAnEtsComponentExpression() && !inEtsNewExpressionContext()) { return parseEtsComponentExpression(); } return parseIdentifier(Diagnostics.Expression_expected); } function parseParenthesizedExpression(): ParenthesizedExpression { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.OpenParenToken); const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); return withJSDoc(finishNode(factory.createParenthesizedExpression(expression), pos), hasJSDoc); } function parseSpreadElement(): Expression { const pos = getNodePos(); parseExpected(SyntaxKind.DotDotDotToken); const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); return finishNode(factory.createSpreadElement(expression), pos); } function parseArgumentOrArrayLiteralElement(): Expression { return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : token() === SyntaxKind.CommaToken ? finishNode(factory.createOmittedExpression(), getNodePos()) : parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); } function parseArgumentExpression(): Expression { let resetSyntaxDataSourceContextFlag: boolean = false; let resetRepeatEachContextFlag: boolean = false; if (inSyntaxComponentContext() && !inSyntaxDataSourceContext() && getFirstArgumentExpression()) { setFirstArgumentExpression(false); if (!getRepeatEachRest()) { setSyntaxDataSourceContext(true); resetSyntaxDataSourceContextFlag = true; } else { resetRepeatEachContextFlag = true; } } const argumentExpressionResult = doOutsideOfContext(disallowInAndDecoratorContext, parseArgumentOrArrayLiteralElement); if (resetSyntaxDataSourceContextFlag) { setSyntaxDataSourceContext(false); } if (resetRepeatEachContextFlag) { setRepeatEachRest(false); } return argumentExpressionResult; } function parseArrayLiteralExpression(): ArrayLiteralExpression { const pos = getNodePos(); const openBracketPosition = scanner.getTokenPos(); const openBracketParsed = parseExpected(SyntaxKind.OpenBracketToken); const multiLine = scanner.hasPrecedingLineBreak(); const elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement); parseExpectedMatchingBrackets(SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken, openBracketParsed, openBracketPosition); return finishNode(factory.createArrayLiteralExpression(elements, multiLine), pos); } function parseObjectLiteralElement(): ObjectLiteralElementLike { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); if (parseOptionalToken(SyntaxKind.DotDotDotToken)) { const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); return withJSDoc(finishNode(factory.createSpreadAssignment(expression), pos), hasJSDoc); } const decorators = parseDecorators(); const modifiers = parseModifiers(); if (parseContextualModifier(SyntaxKind.GetKeyword)) { return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor, SignatureFlags.None); } if (parseContextualModifier(SyntaxKind.SetKeyword)) { return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor, SignatureFlags.None); } const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); const tokenIsIdentifier = isIdentifier(); const name = parsePropertyName(); // Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker. const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); const exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken); if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, exclamationToken); } // check if it is short-hand property assignment or normal property assignment // NOTE: if token is EqualsToken it is interpreted as CoverInitializedName production // CoverInitializedName[Yield] : // IdentifierReference[?Yield] Initializer[In, ?Yield] // this is necessary because ObjectLiteral productions are also used to cover grammar for ObjectAssignmentPattern let node: Mutable; const isShorthandPropertyAssignment = tokenIsIdentifier && (token() !== SyntaxKind.ColonToken); if (isShorthandPropertyAssignment) { const equalsToken = parseOptionalToken(SyntaxKind.EqualsToken); const objectAssignmentInitializer = equalsToken ? allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)) : undefined; node = factory.createShorthandPropertyAssignment(name as Identifier, objectAssignmentInitializer); // Save equals token for error reporting. // TODO(rbuckton): Consider manufacturing this when we need to report an error as it is otherwise not useful. node.equalsToken = equalsToken; } else { parseExpected(SyntaxKind.ColonToken); const initializer = allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)); node = factory.createPropertyAssignment(name, initializer); } // Decorators, Modifiers, questionToken, and exclamationToken are not supported by property assignments and are reported in the grammar checker node.illegalDecorators = decorators; node.modifiers = modifiers; node.questionToken = questionToken; node.exclamationToken = exclamationToken; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseObjectLiteralExpression(): ObjectLiteralExpression { const pos = getNodePos(); const openBracePosition = scanner.getTokenPos(); const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken); const multiLine = scanner.hasPrecedingLineBreak(); const properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimiter*/ true); parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); return finishNode(factory.createObjectLiteralExpression(properties, multiLine), pos); } function parseFunctionExpression(): FunctionExpression { // GeneratorExpression: // function* BindingIdentifier [Yield][opt](FormalParameters[Yield]) { GeneratorBody } // // FunctionExpression: // function BindingIdentifier[opt](FormalParameters) { FunctionBody } const savedDecoratorContext = inDecoratorContext(); setDecoratorContext(/*val*/ false); const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const modifiers = parseModifiers(); parseExpected(SyntaxKind.FunctionKeyword); const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; const name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalBindingIdentifier) : isGenerator ? doInYieldContext(parseOptionalBindingIdentifier) : isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : parseOptionalBindingIdentifier(); const typeParameters = parseTypeParameters(); const parameters = parseParameters(isGenerator | isAsync); const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); const body = parseFunctionBlock(isGenerator | isAsync); setDecoratorContext(savedDecoratorContext); const node = factory.createFunctionExpression(modifiers, asteriskToken, name, typeParameters, parameters, type, body); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseOptionalBindingIdentifier(): Identifier | undefined { return isBindingIdentifier() ? parseBindingIdentifier() : undefined; } function parseNewExpressionOrNewDotTarget(): NewExpression | MetaProperty { setEtsNewExpressionContext(inEtsComponentsContext()); const pos = getNodePos(); parseExpected(SyntaxKind.NewKeyword); if (parseOptional(SyntaxKind.DotToken)) { const name = parseIdentifierName(); return finishNode(factory.createMetaProperty(SyntaxKind.NewKeyword, name), pos); } const expressionPos = getNodePos(); let expression: LeftHandSideExpression = parseMemberExpressionRest(expressionPos, parsePrimaryExpression(), /*allowOptionalChain*/ false); let typeArguments: NodeArray | undefined; // Absorb type arguments into NewExpression when preceding expression is ExpressionWithTypeArguments if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { typeArguments = (expression as ExpressionWithTypeArguments).typeArguments; expression = (expression as ExpressionWithTypeArguments).expression; } if (token() === SyntaxKind.QuestionDotToken) { parseErrorAtCurrentToken(Diagnostics.Invalid_optional_chain_from_new_expression_Did_you_mean_to_call_0, getTextOfNodeFromSourceText(sourceText, expression)); } const argumentList = token() === SyntaxKind.OpenParenToken ? parseArgumentList() : undefined; setEtsNewExpressionContext(false); return finishNode(factory.createNewExpression(expression, typeArguments, argumentList), pos); } // STATEMENTS function parseBlock(ignoreMissingOpenBrace: boolean, diagnosticMessage?: DiagnosticMessage): Block { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const openBracePosition = scanner.getTokenPos(); const openBraceParsed = parseExpected(SyntaxKind.OpenBraceToken, diagnosticMessage); if (openBraceParsed || ignoreMissingOpenBrace) { const multiLine = scanner.hasPrecedingLineBreak(); const statements = parseList(ParsingContext.BlockStatements, parseStatement); parseExpectedMatchingBrackets(SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken, openBraceParsed, openBracePosition); const result = withJSDoc(finishNode(factory.createBlock(statements, multiLine), pos), hasJSDoc); if (token() === SyntaxKind.EqualsToken) { parseErrorAtCurrentToken(Diagnostics.Declaration_or_statement_expected_This_follows_a_block_of_statements_so_if_you_intended_to_write_a_destructuring_assignment_you_might_need_to_wrap_the_the_whole_assignment_in_parentheses); nextToken(); } return result; } else { const statements = createMissingList(); return withJSDoc(finishNode(factory.createBlock(statements, /*multiLine*/ undefined), pos), hasJSDoc); } } function parseFunctionBlock(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block { const savedYieldContext = inYieldContext(); setYieldContext(!!(flags & SignatureFlags.Yield)); const savedAwaitContext = inAwaitContext(); setAwaitContext(!!(flags & SignatureFlags.Await)); const savedTopLevel = topLevel; topLevel = false; // 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. const saveDecoratorContext = inDecoratorContext(); if (saveDecoratorContext) { setDecoratorContext(/*val*/ false); } const block = parseBlock(!!(flags & SignatureFlags.IgnoreMissingOpenBrace), diagnosticMessage); if (saveDecoratorContext) { setDecoratorContext(/*val*/ true); } topLevel = savedTopLevel; setYieldContext(savedYieldContext); setAwaitContext(savedAwaitContext); return block; } function parseEmptyStatement(): Statement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.SemicolonToken); return withJSDoc(finishNode(factory.createEmptyStatement(), pos), hasJSDoc); } function parseIfStatement(): IfStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.IfKeyword); const openParenPosition = scanner.getTokenPos(); const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); const expression = allowInAnd(parseExpression); parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); const thenStatement = parseStatement(); const elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined; return withJSDoc(finishNode(factory.createIfStatement(expression, thenStatement, elseStatement), pos), hasJSDoc); } function parseDoStatement(): DoStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.DoKeyword); const statement = parseStatement(); parseExpected(SyntaxKind.WhileKeyword); const openParenPosition = scanner.getTokenPos(); const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); const expression = allowInAnd(parseExpression); parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); // 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 withJSDoc(finishNode(factory.createDoStatement(statement, expression), pos), hasJSDoc); } function parseWhileStatement(): WhileStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.WhileKeyword); const openParenPosition = scanner.getTokenPos(); const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); const expression = allowInAnd(parseExpression); parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); const statement = parseStatement(); return withJSDoc(finishNode(factory.createWhileStatement(expression, statement), pos), hasJSDoc); } function parseForOrForInOrForOfStatement(): Statement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.ForKeyword); const awaitToken = parseOptionalToken(SyntaxKind.AwaitKeyword); parseExpected(SyntaxKind.OpenParenToken); let initializer!: VariableDeclarationList | Expression; if (token() !== SyntaxKind.SemicolonToken) { if (token() === SyntaxKind.VarKeyword || token() === SyntaxKind.LetKeyword || token() === SyntaxKind.ConstKeyword) { initializer = parseVariableDeclarationList(/*inForStatementInitializer*/ true); } else { initializer = disallowInAnd(parseExpression); } } let node: IterationStatement; if (awaitToken ? parseExpected(SyntaxKind.OfKeyword) : parseOptional(SyntaxKind.OfKeyword)) { const expression = allowInAnd(() => parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true)); parseExpected(SyntaxKind.CloseParenToken); node = factory.createForOfStatement(awaitToken, initializer, expression, parseStatement()); } else if (parseOptional(SyntaxKind.InKeyword)) { const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); node = factory.createForInStatement(initializer, expression, parseStatement()); } else { parseExpected(SyntaxKind.SemicolonToken); const condition = token() !== SyntaxKind.SemicolonToken && token() !== SyntaxKind.CloseParenToken ? allowInAnd(parseExpression) : undefined; parseExpected(SyntaxKind.SemicolonToken); const incrementor = token() !== SyntaxKind.CloseParenToken ? allowInAnd(parseExpression) : undefined; parseExpected(SyntaxKind.CloseParenToken); node = factory.createForStatement(initializer, condition, incrementor, parseStatement()); } return withJSDoc(finishNode(node, pos) as ForStatement | ForInOrOfStatement, hasJSDoc); } function parseBreakOrContinueStatement(kind: SyntaxKind): BreakOrContinueStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); const label = canParseSemicolon() ? undefined : parseIdentifier(); parseSemicolon(); const node = kind === SyntaxKind.BreakStatement ? factory.createBreakStatement(label) : factory.createContinueStatement(label); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseReturnStatement(): ReturnStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.ReturnKeyword); const expression = canParseSemicolon() ? undefined : allowInAnd(parseExpression); parseSemicolon(); return withJSDoc(finishNode(factory.createReturnStatement(expression), pos), hasJSDoc); } function parseWithStatement(): WithStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.WithKeyword); const openParenPosition = scanner.getTokenPos(); const openParenParsed = parseExpected(SyntaxKind.OpenParenToken); const expression = allowInAnd(parseExpression); parseExpectedMatchingBrackets(SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken, openParenParsed, openParenPosition); const statement = doInsideOfContext(NodeFlags.InWithStatement, parseStatement); return withJSDoc(finishNode(factory.createWithStatement(expression, statement), pos), hasJSDoc); } function parseCaseClause(): CaseClause { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.CaseKeyword); const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.ColonToken); const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); return withJSDoc(finishNode(factory.createCaseClause(expression, statements), pos), hasJSDoc); } function parseDefaultClause(): DefaultClause { const pos = getNodePos(); parseExpected(SyntaxKind.DefaultKeyword); parseExpected(SyntaxKind.ColonToken); const statements = parseList(ParsingContext.SwitchClauseStatements, parseStatement); return finishNode(factory.createDefaultClause(statements), pos); } function parseCaseOrDefaultClause(): CaseOrDefaultClause { return token() === SyntaxKind.CaseKeyword ? parseCaseClause() : parseDefaultClause(); } function parseCaseBlock(): CaseBlock { const pos = getNodePos(); parseExpected(SyntaxKind.OpenBraceToken); const clauses = parseList(ParsingContext.SwitchClauses, parseCaseOrDefaultClause); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(factory.createCaseBlock(clauses), pos); } function parseSwitchStatement(): SwitchStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.SwitchKeyword); parseExpected(SyntaxKind.OpenParenToken); const expression = allowInAnd(parseExpression); parseExpected(SyntaxKind.CloseParenToken); const caseBlock = parseCaseBlock(); return withJSDoc(finishNode(factory.createSwitchStatement(expression, caseBlock), pos), hasJSDoc); } function parseThrowStatement(): ThrowStatement { // ThrowStatement[Yield] : // throw [no LineTerminator here]Expression[In, ?Yield]; const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.ThrowKeyword); // 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. // Instead, we create a "missing" identifier, but don't report an error. The actual error // will be reported in the grammar walker. let expression = scanner.hasPrecedingLineBreak() ? undefined : allowInAnd(parseExpression); if (expression === undefined) { identifierCount++; expression = finishNode(factory.createIdentifier(""), getNodePos()); } if (!tryParseSemicolon()) { parseErrorForMissingSemicolonAfter(expression); } return withJSDoc(finishNode(factory.createThrowStatement(expression), pos), hasJSDoc); } // TODO: Review for error recovery function parseTryStatement(): TryStatement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.TryKeyword); const tryBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); const 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. let finallyBlock: Block | undefined; if (!catchClause || token() === SyntaxKind.FinallyKeyword) { parseExpected(SyntaxKind.FinallyKeyword, Diagnostics.catch_or_finally_expected); finallyBlock = parseBlock(/*ignoreMissingOpenBrace*/ false); } return withJSDoc(finishNode(factory.createTryStatement(tryBlock, catchClause, finallyBlock), pos), hasJSDoc); } function parseCatchClause(): CatchClause { const pos = getNodePos(); parseExpected(SyntaxKind.CatchKeyword); let variableDeclaration; if (parseOptional(SyntaxKind.OpenParenToken)) { variableDeclaration = parseVariableDeclaration(); parseExpected(SyntaxKind.CloseParenToken); } else { // Keep shape of node to avoid degrading performance. variableDeclaration = undefined; } const block = parseBlock(/*ignoreMissingOpenBrace*/ false); return finishNode(factory.createCatchClause(variableDeclaration, block), pos); } function parseDebuggerStatement(): Statement { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); parseExpected(SyntaxKind.DebuggerKeyword); parseSemicolon(); return withJSDoc(finishNode(factory.createDebuggerStatement(), pos), hasJSDoc); } 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. const pos = getNodePos(); let hasJSDoc = hasPrecedingJSDocComment(); let node: ExpressionStatement | LabeledStatement; const hasParen = token() === SyntaxKind.OpenParenToken; const expression = allowInAnd(parseExpression); if (ts.isIdentifier(expression) && parseOptional(SyntaxKind.ColonToken)) { node = factory.createLabeledStatement(expression, parseStatement()); } else { if (!tryParseSemicolon()) { parseErrorForMissingSemicolonAfter(expression); } node = factory.createExpressionStatement(expression); if (hasParen) { // do not parse the same jsdoc twice hasJSDoc = false; } } return withJSDoc(finishNode(node, pos), hasJSDoc); } function nextTokenIsIdentifierOrKeywordOnSameLine() { nextToken(); return tokenIsIdentifierOrKeyword(token()) && !scanner.hasPrecedingLineBreak(); } function nextTokenIsClassKeywordOnSameLine() { nextToken(); return token() === SyntaxKind.ClassKeyword && !scanner.hasPrecedingLineBreak(); } function nextTokenIsFunctionKeywordOnSameLine() { nextToken(); return token() === SyntaxKind.FunctionKeyword && !scanner.hasPrecedingLineBreak(); } function nextTokenIsIdentifierOrKeywordOrLiteralOnSameLine() { nextToken(); return (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.BigIntLiteral || token() === SyntaxKind.StringLiteral) && !scanner.hasPrecedingLineBreak(); } function isDeclaration(): boolean { while (true) { switch (token()) { case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.FunctionKeyword: case SyntaxKind.ClassKeyword: case SyntaxKind.EnumKeyword: return true; case SyntaxKind.StructKeyword: return inEtsContext(); case SyntaxKind.AtToken: return inAllowAnnotationContext() && nextToken() === SyntaxKind.InterfaceKeyword; // 'declare', 'module', 'namespace', 'interface'* and 'type' are all legal JavaScript identifiers; // however, an identifier cannot be followed by another identifier on the same line. This is what we // count on to parse out the respective declarations. For instance, we exploit this to say that // // namespace n // // can be none other than the beginning of a namespace declaration, but need to respect that JavaScript sees // // namespace // n // // as the identifier 'namespace' on one line followed by the identifier 'n' on another. // We need to look one token ahead to see if it permissible to try parsing a declaration. // // *Note*: 'interface' is actually a strict mode reserved word. So while // // "use strict" // interface // I {} // // could be legal, it would add complexity for very little gain. case SyntaxKind.InterfaceKeyword: case SyntaxKind.TypeKeyword: return nextTokenIsIdentifierOnSameLine(); case SyntaxKind.ModuleKeyword: case SyntaxKind.NamespaceKeyword: return nextTokenIsIdentifierOrStringLiteralOnSameLine(); case SyntaxKind.AbstractKeyword: case SyntaxKind.AccessorKeyword: case SyntaxKind.AsyncKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PublicKeyword: case SyntaxKind.ReadonlyKeyword: nextToken(); // ASI takes effect for this modifier. if (scanner.hasPrecedingLineBreak()) { return false; } continue; case SyntaxKind.GlobalKeyword: nextToken(); return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.Identifier || token() === SyntaxKind.ExportKeyword; case SyntaxKind.ImportKeyword: nextToken(); return token() === SyntaxKind.StringLiteral || token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken || tokenIsIdentifierOrKeyword(token()); case SyntaxKind.ExportKeyword: let currentToken = nextToken(); if (currentToken === SyntaxKind.TypeKeyword) { currentToken = lookAhead(nextToken); } if (currentToken === SyntaxKind.EqualsToken || currentToken === SyntaxKind.AsteriskToken || currentToken === SyntaxKind.OpenBraceToken || currentToken === SyntaxKind.DefaultKeyword || currentToken === SyntaxKind.AsKeyword) { return true; } continue; case SyntaxKind.StaticKeyword: nextToken(); continue; default: return false; } } } function isStartOfDeclaration(): boolean { return lookAhead(isDeclaration); } function isStartOfStatement(): boolean { 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. // falls through case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: return true; case SyntaxKind.StructKeyword: return inEtsContext(); case SyntaxKind.ImportKeyword: return isStartOfDeclaration() || lookAhead(nextTokenIsOpenParenOrLessThanOrDot); case SyntaxKind.ConstKeyword: case SyntaxKind.ExportKeyword: return isStartOfDeclaration(); case SyntaxKind.AsyncKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.InterfaceKeyword: case SyntaxKind.ModuleKeyword: case SyntaxKind.NamespaceKeyword: case SyntaxKind.TypeKeyword: case SyntaxKind.GlobalKeyword: // When these don't start a declaration, they're an identifier in an expression statement return true; case SyntaxKind.AccessorKeyword: case SyntaxKind.PublicKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.StaticKeyword: case SyntaxKind.ReadonlyKeyword: // 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 isStartOfDeclaration() || !lookAhead(nextTokenIsIdentifierOrKeywordOnSameLine); default: return isStartOfExpression(); } } function nextTokenIsBindingIdentifierOrStartOfDestructuring() { nextToken(); return isBindingIdentifier() || token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken; } function isLetDeclaration() { // In ES6 'let' always starts a lexical declaration if followed by an identifier or { // or [. return lookAhead(nextTokenIsBindingIdentifierOrStartOfDestructuring); } function parseStatement(): Statement { switch (token()) { case SyntaxKind.SemicolonToken: return parseEmptyStatement(); case SyntaxKind.OpenBraceToken: return parseBlock(/*ignoreMissingOpenBrace*/ false); case SyntaxKind.VarKeyword: return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); case SyntaxKind.LetKeyword: if (isLetDeclaration()) { return parseVariableStatement(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); } break; case SyntaxKind.FunctionKeyword: return parseFunctionDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); case SyntaxKind.StructKeyword: if (inEtsContext()) { return parseStructDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined); } break; case SyntaxKind.ClassKeyword: return parseClassDeclaration(getNodePos(), hasPrecedingJSDocComment(), /*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 'catch' and 'finally' for error recovery. // falls through case SyntaxKind.CatchKeyword: case SyntaxKind.FinallyKeyword: return parseTryStatement(); case SyntaxKind.DebuggerKeyword: return parseDebuggerStatement(); case SyntaxKind.AtToken: return parseDeclaration(); case SyntaxKind.AsyncKeyword: case SyntaxKind.InterfaceKeyword: case SyntaxKind.TypeKeyword: case SyntaxKind.ModuleKeyword: case SyntaxKind.NamespaceKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.EnumKeyword: case SyntaxKind.ExportKeyword: case SyntaxKind.ImportKeyword: case SyntaxKind.PrivateKeyword: case SyntaxKind.ProtectedKeyword: case SyntaxKind.PublicKeyword: case SyntaxKind.AbstractKeyword: case SyntaxKind.AccessorKeyword: case SyntaxKind.StaticKeyword: case SyntaxKind.ReadonlyKeyword: case SyntaxKind.GlobalKeyword: if (isStartOfDeclaration()) { return parseDeclaration(); } break; } return parseExpressionOrLabeledStatement(); } function isDeclareModifier(modifier: Modifier) { return modifier.kind === SyntaxKind.DeclareKeyword; } function parseAnnotationDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): AnnotationDeclaration { const atTokenPos = scanner.getTokenPos(); parseExpected(SyntaxKind.AtToken); const interfaceTokenPos = scanner.getTokenPos(); parseExpected(SyntaxKind.InterfaceKeyword); // Prevent any tokens between '@' and 'interface' if (interfaceTokenPos - atTokenPos > 1) { parseErrorAt(atTokenPos + 1, interfaceTokenPos, Diagnostics.In_annotation_declaration_any_symbols_between_and_interface_are_forbidden); } const name = createIdentifier(isBindingIdentifier()); let members; if (parseExpected(SyntaxKind.OpenBraceToken)) { members = parseAnnotationMembers(); parseExpected(SyntaxKind.CloseBraceToken); } else { members = createMissingList(); } const node = factory.createAnnotationDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, members); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseDeclaration(): Statement { // `parseListElement` attempted to get the reused node at this position, // but the ambient context flag was not yet set, so the node appeared // not reusable in that context. const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const decorators = parseDecorators(); if (token() === SyntaxKind.FunctionKeyword || token() === SyntaxKind.ExportKeyword) { if (hasEtsExtendDecoratorNames(decorators, sourceFileCompilerOptions)) { const extendEtsComponentDecoratorNames = getEtsExtendDecoratorsComponentNames(decorators, sourceFileCompilerOptions); if (extendEtsComponentDecoratorNames.length > 0) { sourceFileCompilerOptions.ets?.extend.components.forEach(({ name, type, instance }) => { if (name === last(extendEtsComponentDecoratorNames)) { extendEtsComponentDeclaration = { name, type, instance }; } }); } setEtsExtendComponentsContext(!!extendEtsComponentDeclaration); } else if (hasEtsStylesDecoratorNames(decorators, sourceFileCompilerOptions)) { const stylesEtsComponentDecoratorNames = getEtsStylesDecoratorComponentNames(decorators, sourceFileCompilerOptions); if (stylesEtsComponentDecoratorNames.length > 0) { stylesEtsComponentDeclaration = sourceFileCompilerOptions.ets?.styles.component; } setEtsStylesComponentsContext(!!stylesEtsComponentDeclaration); } else { setEtsComponentsContext(isTokenInsideBuilder(decorators, sourceFileCompilerOptions)); } } const modifiers = parseModifiers(); const isAmbient = some(modifiers, isDeclareModifier); if (isAmbient) { const node = tryReuseAmbientDeclaration(pos); if (node) { return node; } for (const m of modifiers!) { (m as Mutable).flags |= NodeFlags.Ambient; } return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers)); } else { return parseDeclarationWorker(pos, hasJSDoc, decorators, modifiers); } } function tryReuseAmbientDeclaration(pos: number): Statement | undefined { return doInsideOfContext(NodeFlags.Ambient, () => { const node = currentNode(parsingContext, pos); if (node) { return consumeNode(node) as Statement; } }); } function parseDeclarationWorker(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): Statement { switch (token()) { case SyntaxKind.VarKeyword: case SyntaxKind.LetKeyword: case SyntaxKind.ConstKeyword: return parseVariableStatement(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.FunctionKeyword: return parseFunctionDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.ClassKeyword: return parseClassDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.StructKeyword: if (inEtsContext()) { return parseStructDeclaration(pos, hasJSDoc, decorators, modifiers); } return parseDeclarationDefault(pos,decorators, modifiers); case SyntaxKind.AtToken: if (inAllowAnnotationContext() && lookAhead(() => nextToken() === SyntaxKind.InterfaceKeyword)) { return parseAnnotationDeclaration(pos, hasJSDoc, decorators, modifiers); } return parseDeclarationDefault(pos, decorators, modifiers); case SyntaxKind.InterfaceKeyword: return parseInterfaceDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.TypeKeyword: return parseTypeAliasDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.EnumKeyword: return parseEnumDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.GlobalKeyword: case SyntaxKind.ModuleKeyword: case SyntaxKind.NamespaceKeyword: return parseModuleDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.ImportKeyword: return parseImportDeclarationOrImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.ExportKeyword: nextToken(); switch (token()) { case SyntaxKind.DefaultKeyword: case SyntaxKind.EqualsToken: return parseExportAssignment(pos, hasJSDoc, decorators, modifiers); case SyntaxKind.AsKeyword: return parseNamespaceExportDeclaration(pos, hasJSDoc, decorators, modifiers); default: return parseExportDeclaration(pos, hasJSDoc, decorators, modifiers); } default: return parseDeclarationDefault(pos,decorators, modifiers); } } function parseDeclarationDefault(pos: number,decorators: NodeArray | undefined, modifiers: NodeArray | undefined): Statement { if (decorators || modifiers) { // 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. const missing = createMissingNode(SyntaxKind.MissingDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); setTextRangePos(missing, pos); missing.illegalDecorators = decorators; missing.modifiers = modifiers; return missing; } return undefined!; // TODO: GH#18217 } function nextTokenIsIdentifierOrStringLiteralOnSameLine() { nextToken(); return !scanner.hasPrecedingLineBreak() && (isIdentifier() || token() === SyntaxKind.StringLiteral); } function parseFunctionBlockOrSemicolon(flags: SignatureFlags, diagnosticMessage?: DiagnosticMessage): Block | undefined { if (token() !== SyntaxKind.OpenBraceToken) { if (flags & SignatureFlags.Type) { parseTypeMemberSemicolon(); return; } if (canParseSemicolon()) { parseSemicolon(); return; } } return parseFunctionBlock(flags, diagnosticMessage); } // DECLARATIONS function parseArrayBindingElement(): ArrayBindingElement { const pos = getNodePos(); if (token() === SyntaxKind.CommaToken) { return finishNode(factory.createOmittedExpression(), pos); } const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); const name = parseIdentifierOrPattern(); const initializer = parseInitializer(); return finishNode(factory.createBindingElement(dotDotDotToken, /*propertyName*/ undefined, name, initializer), pos); } function parseObjectBindingElement(): BindingElement { const pos = getNodePos(); const dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken); const tokenIsIdentifier = isBindingIdentifier(); let propertyName: PropertyName | undefined = parsePropertyName(); let name: BindingName; if (tokenIsIdentifier && token() !== SyntaxKind.ColonToken) { name = propertyName as Identifier; propertyName = undefined; } else { parseExpected(SyntaxKind.ColonToken); name = parseIdentifierOrPattern(); } const initializer = parseInitializer(); return finishNode(factory.createBindingElement(dotDotDotToken, propertyName, name, initializer), pos); } function parseObjectBindingPattern(): ObjectBindingPattern { const pos = getNodePos(); parseExpected(SyntaxKind.OpenBraceToken); const elements = parseDelimitedList(ParsingContext.ObjectBindingElements, parseObjectBindingElement); parseExpected(SyntaxKind.CloseBraceToken); return finishNode(factory.createObjectBindingPattern(elements), pos); } function parseArrayBindingPattern(): ArrayBindingPattern { const pos = getNodePos(); parseExpected(SyntaxKind.OpenBracketToken); const elements = parseDelimitedList(ParsingContext.ArrayBindingElements, parseArrayBindingElement); parseExpected(SyntaxKind.CloseBracketToken); return finishNode(factory.createArrayBindingPattern(elements), pos); } function isBindingIdentifierOrPrivateIdentifierOrPattern() { return token() === SyntaxKind.OpenBraceToken || token() === SyntaxKind.OpenBracketToken || token() === SyntaxKind.PrivateIdentifier || isBindingIdentifier(); } function parseIdentifierOrPattern(privateIdentifierDiagnosticMessage?: DiagnosticMessage): Identifier | BindingPattern { if (token() === SyntaxKind.OpenBracketToken) { return parseArrayBindingPattern(); } if (token() === SyntaxKind.OpenBraceToken) { return parseObjectBindingPattern(); } return parseBindingIdentifier(privateIdentifierDiagnosticMessage); } function parseVariableDeclarationAllowExclamation() { return parseVariableDeclaration(/*allowExclamation*/ true); } function parseVariableDeclaration(allowExclamation?: boolean): VariableDeclaration { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const name = parseIdentifierOrPattern(Diagnostics.Private_identifiers_are_not_allowed_in_variable_declarations); let exclamationToken: ExclamationToken | undefined; if (allowExclamation && name.kind === SyntaxKind.Identifier && token() === SyntaxKind.ExclamationToken && !scanner.hasPrecedingLineBreak()) { exclamationToken = parseTokenNode>(); } const type = parseTypeAnnotation(); const initializer = isInOrOfKeyword(token()) ? undefined : parseInitializer(); const node = factory.createVariableDeclaration(name, exclamationToken, type, initializer); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseVariableDeclarationList(inForStatementInitializer: boolean): VariableDeclarationList { const pos = getNodePos(); let flags: NodeFlags = 0; switch (token()) { case SyntaxKind.VarKeyword: break; case SyntaxKind.LetKeyword: flags |= NodeFlags.Let; break; case SyntaxKind.ConstKeyword: 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. let declarations: readonly VariableDeclaration[]; if (token() === SyntaxKind.OfKeyword && lookAhead(canFollowContextualOfKeyword)) { declarations = createMissingList(); } else { const savedDisallowIn = inDisallowInContext(); setDisallowInContext(inForStatementInitializer); declarations = parseDelimitedList(ParsingContext.VariableDeclarations, inForStatementInitializer ? parseVariableDeclaration : parseVariableDeclarationAllowExclamation); setDisallowInContext(savedDisallowIn); } return finishNode(factory.createVariableDeclarationList(declarations, flags), pos); } function canFollowContextualOfKeyword(): boolean { return nextTokenIsIdentifier() && nextToken() === SyntaxKind.CloseParenToken; } function parseVariableStatement(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): VariableStatement { const declarationList = parseVariableDeclarationList(/*inForStatementInitializer*/ false); parseSemicolon(); const node = factory.createVariableStatement(modifiers, declarationList); // Decorators are not allowed on a variable statement, so we keep track of them to report them in the grammar checker. node.illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseFunctionDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): FunctionDeclaration { const savedAwaitContext = inAwaitContext(); const modifierFlags = modifiersToFlags(modifiers); parseExpected(SyntaxKind.FunctionKeyword); const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); // We don't parse the name here in await context, instead we will report a grammar error in the checker. const name = modifierFlags & ModifierFlags.Default ? parseOptionalBindingIdentifier() : parseBindingIdentifier(); if(name && hasEtsStylesDecoratorNames(decorators, sourceFileCompilerOptions)) { fileStylesComponents.set(name.escapedText.toString(), SyntaxKind.FunctionDeclaration); } const originalUICallbackContext = inUICallbackContext(); setEtsBuilderContext(hasEtsBuilderDecoratorNames(decorators, sourceFileCompilerOptions)); setUICallbackContext(inBuilderContext()); const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; const isAsync = modifierFlags & ModifierFlags.Async ? SignatureFlags.Await : SignatureFlags.None; const typeParameters = inEtsStylesComponentsContext() && stylesEtsComponentDeclaration ? parseEtsTypeParameters(scanner.getStartPos()) : parseTypeParameters(); if (modifierFlags & ModifierFlags.Export) setAwaitContext(/*value*/ true); const parameters = parseParameters(isGenerator | isAsync); const typeStartPos = scanner.getStartPos(); const type = getFunctionDeclarationReturnType(); const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, Diagnostics.or_expected); setEtsBuilderContext(false); setUICallbackContext(originalUICallbackContext); setEtsExtendComponentsContext(false); extendEtsComponentDeclaration = undefined; setEtsStylesComponentsContext(false); stylesEtsComponentDeclaration = undefined; setEtsComponentsContext(inBuildContext()); setAwaitContext(savedAwaitContext); const node = factory.createFunctionDeclaration(modifiers, asteriskToken, name, typeParameters, parameters, type, body); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); function getFunctionDeclarationReturnType(): TypeNode | undefined { let returnType = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); // If function decorated by Extend and type is not defined, then will use Ets Extend Components Type if (!returnType && extendEtsComponentDeclaration && inEtsExtendComponentsContext()) { returnType = finishVirtualNode(factory.createTypeReferenceNode( finishVirtualNode(factory.createIdentifier(extendEtsComponentDeclaration.type), typeStartPos, typeStartPos)), typeStartPos, typeStartPos); } // If function decorated by Styles and type is not defined, then will use Ets Styles Components Type if (!returnType && stylesEtsComponentDeclaration && inEtsStylesComponentsContext()) { returnType = finishVirtualNode(factory.createTypeReferenceNode( finishVirtualNode(factory.createIdentifier(stylesEtsComponentDeclaration.type), typeStartPos, typeStartPos)), typeStartPos, typeStartPos); } return returnType; } } function parseConstructorName() { if (token() === SyntaxKind.ConstructorKeyword) { return parseExpected(SyntaxKind.ConstructorKeyword); } if (token() === SyntaxKind.StringLiteral && lookAhead(nextToken) === SyntaxKind.OpenParenToken) { return tryParse(() => { const literalNode = parseLiteralNode(); return literalNode.text === "constructor" ? literalNode : undefined; }); } } function tryParseConstructorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ConstructorDeclaration | undefined { return tryParse(() => { if (parseConstructorName()) { const typeParameters = parseTypeParameters(); const parameters = parseParameters(SignatureFlags.None); const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); const body = parseFunctionBlockOrSemicolon(SignatureFlags.None, Diagnostics.or_expected); const node = factory.createConstructorDeclaration(modifiers, parameters, body); // Attach invalid nodes if they exist so that we can report them in the grammar checker. (node as Mutable).illegalDecorators = decorators; (node as Mutable).typeParameters = typeParameters; (node as Mutable).type = type; return withJSDoc(finishNode(node, pos), hasJSDoc); } }); } function isTokenInsideStructBuild(methodName: PropertyName): boolean { const renderMethod = sourceFileCompilerOptions.ets?.render?.method?.find(render => render === "build") ?? "build"; if (methodName.kind === SyntaxKind.Identifier && methodName.escapedText === renderMethod) { return true; } return false; } function isTokenInsideStructBuilder(decorators: NodeArray | undefined): boolean { return isTokenInsideBuilder(decorators, sourceFileCompilerOptions); } function isTokenInsideStructPageTransition(methodName: PropertyName): boolean { const renderMethod = sourceFileCompilerOptions.ets?.render?.method?.find(render => render === "pageTransition") ?? "pageTransition"; if (methodName.kind === SyntaxKind.Identifier && methodName.escapedText === renderMethod) { return true; } return false; } function parseMethodDeclaration( pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, asteriskToken: AsteriskToken | undefined, name: PropertyName, questionToken: QuestionToken | undefined, exclamationToken: ExclamationToken | undefined, diagnosticMessage?: DiagnosticMessage ): MethodDeclaration { const methodName = getPropertyNameForPropertyNameNode(name)?.toString(); const orignalEtsBuildContext = inBuildContext(); const orignalEtsBuilderContext = inBuilderContext(); const orignalUICallbackContext = inUICallbackContext(); setEtsBuildContext(methodName === sourceFileCompilerOptions?.ets?.render?.method?.find(render => render === "build")); setEtsBuilderContext(hasEtsBuilderDecoratorNames(decorators, sourceFileCompilerOptions)); setUICallbackContext(inBuildContext() || inBuilderContext()); if (inStructContext() && hasEtsStylesDecoratorNames(decorators, sourceFileCompilerOptions)) { if (methodName && currentStructName) { structStylesComponents.set(methodName, { structName: currentStructName, kind: SyntaxKind.MethodDeclaration }); } const stylesEtsComponentDecoratorNames = getEtsStylesDecoratorComponentNames(decorators, sourceFileCompilerOptions); if (stylesEtsComponentDecoratorNames.length > 0) { stylesEtsComponentDeclaration = sourceFileCompilerOptions.ets?.styles.component; } setEtsStylesComponentsContext(!!stylesEtsComponentDeclaration); } const orignalEtsComponentsContext: boolean = inEtsComponentsContext(); setEtsComponentsContext(inStructContext() && (isTokenInsideStructBuild(name) || isTokenInsideStructBuilder(decorators) || isTokenInsideStructPageTransition(name))); const isGenerator = asteriskToken ? SignatureFlags.Yield : SignatureFlags.None; const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; const typeParameters = inEtsStylesComponentsContext() && stylesEtsComponentDeclaration ? parseEtsTypeParameters(pos) : parseTypeParameters(); const parameters = parseParameters(isGenerator | isAsync); const typeStartPos = scanner.getStartPos(); const type = getMethodDeclarationReturnType(); const body = parseFunctionBlockOrSemicolon(isGenerator | isAsync, diagnosticMessage); const node = factory.createMethodDeclaration( combineDecoratorsAndModifiers(decorators, modifiers), asteriskToken, name, questionToken, typeParameters, parameters, type, body ); // An exclamation token on a method is invalid syntax and will be handled by the grammar checker (node as Mutable).exclamationToken = exclamationToken; setEtsBuildContext(orignalEtsBuildContext); setEtsBuilderContext(orignalEtsBuilderContext); setUICallbackContext(orignalUICallbackContext); setEtsStylesComponentsContext(false); stylesEtsComponentDeclaration = undefined; setEtsComponentsContext(orignalEtsComponentsContext); return withJSDoc(finishNode(node, pos), hasJSDoc); function getMethodDeclarationReturnType(): TypeNode | undefined { let returnType = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); // If function decorated by Styles and type is not defined, then will use Ets Styles Components Type if (!returnType && stylesEtsComponentDeclaration && inEtsStylesComponentsContext()) { returnType = finishVirtualNode(factory.createTypeReferenceNode( finishVirtualNode(factory.createIdentifier(stylesEtsComponentDeclaration.type), typeStartPos, typeStartPos)), typeStartPos, typeStartPos); } return returnType; } } function parseAnnotationPropertyDeclaration( pos: number, hasJSDoc: boolean, name: PropertyName ): AnnotationPropertyDeclaration { const type = parseTypeAnnotation(); const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); parseSemicolonAfterPropertyName(name, type, initializer); const node = factory.createAnnotationPropertyDeclaration( name, type, initializer); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parsePropertyDeclaration( pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, name: PropertyName, questionToken: QuestionToken | undefined ): PropertyDeclaration { const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined; const type = parseTypeAnnotation(); const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer); parseSemicolonAfterPropertyName(name, type, initializer); const node = factory.createPropertyDeclaration( combineDecoratorsAndModifiers(decorators, modifiers), name, questionToken || exclamationToken, type, initializer); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parsePropertyOrMethodDeclaration( pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined ): PropertyDeclaration | MethodDeclaration { const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); const 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. const questionToken = parseOptionalToken(SyntaxKind.QuestionToken); if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) { return parseMethodDeclaration(pos, hasJSDoc, decorators, modifiers, asteriskToken, name, questionToken, /*exclamationToken*/ undefined, Diagnostics.or_expected); } return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, questionToken); } function parseAccessorDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: AccessorDeclaration["kind"], flags: SignatureFlags): AccessorDeclaration { const name = parsePropertyName(); const typeParameters = parseTypeParameters(); const parameters = parseParameters(SignatureFlags.None); const type = parseReturnType(SyntaxKind.ColonToken, /*isType*/ false); const body = parseFunctionBlockOrSemicolon(flags); const node = kind === SyntaxKind.GetAccessor ? factory.createGetAccessorDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, parameters, type, body) : factory.createSetAccessorDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, parameters, body); // Keep track of `typeParameters` (for both) and `type` (for setters) if they were parsed those indicate grammar errors (node as Mutable).typeParameters = typeParameters; if (isSetAccessorDeclaration(node)) (node as Mutable).type = type; return withJSDoc(finishNode(node, pos), hasJSDoc); } function isAnnotationMemberStart(): boolean { let idToken: SyntaxKind | undefined; if (token() === SyntaxKind.AtToken) { return false; } if (isModifierKind(token())) { return false; } if (token() === SyntaxKind.AsteriskToken) { return false; } // 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 prohibited for annotations if (token() === SyntaxKind.OpenBracketToken) { return false; } // 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 we no need to parse. if (isKeyword(idToken)) { return false; } // 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.ColonToken: // Type Annotation for declaration case SyntaxKind.EqualsToken: // Initializer for declaration 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 isClassMemberStart(): boolean { let idToken: SyntaxKind | undefined; if (token() === SyntaxKind.AtToken) { if (inAllowAnnotationContext() && lookAhead(() => nextToken() === SyntaxKind.InterfaceKeyword)) { return false; } return true; } // Eat up all modifiers, but hold on to the last one in case it is actually an identifier. while (isModifierKind(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.ExclamationToken: // Non-null assertion on property name 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 parseClassStaticBlockDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: ModifiersArray | undefined): ClassStaticBlockDeclaration { parseExpectedToken(SyntaxKind.StaticKeyword); const body = parseClassStaticBlockBody(); const node = withJSDoc(finishNode(factory.createClassStaticBlockDeclaration(body), pos), hasJSDoc); (node as Mutable).illegalDecorators = decorators; (node as Mutable).modifiers = modifiers; return node; } function parseClassStaticBlockBody() { const savedYieldContext = inYieldContext(); const savedAwaitContext = inAwaitContext(); setYieldContext(false); setAwaitContext(true); const body = parseBlock(/*ignoreMissingOpenBrace*/ false); setYieldContext(savedYieldContext); setAwaitContext(savedAwaitContext); return body; } function parseDecoratorExpression() { if (inAwaitContext() && token() === SyntaxKind.AwaitKeyword) { // `@await` is is disallowed in an [Await] context, but can cause parsing to go off the rails // This simply parses the missing identifier and moves on. const pos = getNodePos(); const awaitExpression = parseIdentifier(Diagnostics.Expression_expected); nextToken(); const memberExpression = parseMemberExpressionRest(pos, awaitExpression, /*allowOptionalChain*/ true); return parseCallExpressionRest(pos, memberExpression); } return parseLeftHandSideExpressionOrHigher(); } function tryParseDecorator(): Decorator | undefined { const pos = getNodePos(); if (inAllowAnnotationContext() && token() === SyntaxKind.AtToken && lookAhead(() => nextToken() === SyntaxKind.InterfaceKeyword)) { return undefined; } if (!parseOptional(SyntaxKind.AtToken)) { return undefined; } const expression = doInDecoratorContext(parseDecoratorExpression); return finishNode(factory.createDecorator(expression), pos); } function parseDecorators(): NodeArray | undefined { const pos = getNodePos(); let list, decorator; while (decorator = tryParseDecorator()) { list = append(list, decorator); } return list && createNodeArray(list, pos); } function tryParseModifier(permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, hasSeenStaticModifier?: boolean): Modifier | undefined { const pos = getNodePos(); const kind = token(); if (token() === SyntaxKind.ConstKeyword && permitInvalidConstAsModifier) { // We need to ensure that any subsequent modifiers appear on the same line // so that when 'const' is a standalone declaration, we don't issue an error. if (!tryParse(nextTokenIsOnSameLineAndCanFollowModifier)) { return undefined; } } else if (stopOnStartOfClassStaticBlock && token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { return undefined; } else if (hasSeenStaticModifier && token() === SyntaxKind.StaticKeyword) { return undefined; } else { if (!parseAnyContextualModifier()) { return undefined; } } return finishNode(factory.createToken(kind as Modifier["kind"]), pos); } function combineDecoratorsAndModifiers(decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NodeArray | undefined { if (!decorators) return modifiers; if (!modifiers) return decorators; const decoratorsAndModifiers = factory.createNodeArray(concatenate(decorators, modifiers)); setTextRangePosEnd(decoratorsAndModifiers, decorators.pos, modifiers.end); return decoratorsAndModifiers; } function hasParamAndNoOnceDecorator(decorators: NodeArray | undefined): boolean { let hasParamDecorator = false; let hasOnceDecorator = false; decorators?.forEach((decorator) => { if (!ts.isIdentifier(decorator.expression)) { return; } if (decorator.expression.escapedText === 'Param') { hasParamDecorator = true; } else if (decorator.expression.escapedText === 'Once') { hasOnceDecorator = true; } }); return (hasParamDecorator && !hasOnceDecorator); } /* * There are situations in which a modifier like 'const' will appear unexpectedly, such as on a class member. * In those situations, if we are entirely sure that 'const' is not valid on its own (such as when ASI takes effect * and turns it into a standalone declaration), then it is better to parse it and report an error later. * * In such situations, 'permitInvalidConstAsModifier' should be set to true. */ function parseModifiers( permitInvalidConstAsModifier?: boolean, stopOnStartOfClassStaticBlock?: boolean, shouldAddReadonly?: boolean ): NodeArray | undefined { const pos = getNodePos(); let list, modifier, hasSeenStatic = false; let hasReadonly = false; while (modifier = tryParseModifier(permitInvalidConstAsModifier, stopOnStartOfClassStaticBlock, hasSeenStatic)) { if (modifier.kind === SyntaxKind.StaticKeyword) hasSeenStatic = true; if (modifier.kind === SyntaxKind.ReadonlyKeyword) { hasReadonly = true; } list = append(list, modifier); } if (shouldAddReadonly && !hasReadonly) { const readonlyModifier = finishVirtualNode(factory.createToken(SyntaxKind.ReadonlyKeyword)); list = append(list, readonlyModifier); } return list && createNodeArray(list, pos); } function parseModifiersForArrowFunction(): NodeArray | undefined { let modifiers: NodeArray | undefined; if (token() === SyntaxKind.AsyncKeyword) { const pos = getNodePos(); nextToken(); const modifier = finishNode(factory.createToken(SyntaxKind.AsyncKeyword), pos); modifiers = createNodeArray([modifier], pos); } return modifiers; } function parseClassElement(): ClassElement { const pos = getNodePos(); if (token() === SyntaxKind.SemicolonToken) { nextToken(); return finishNode(factory.createSemicolonClassElement(), pos); } const hasJSDoc = hasPrecedingJSDocComment(); const decorators = parseDecorators(); /* * shouldAddReadonly adds readonly modifier when the element in struct has the Param decorator * and doesn't have Once decorator. */ const shouldAddReadonly = inStructContext() && hasParamAndNoOnceDecorator(decorators); const modifiers = parseModifiers(/*permitInvalidConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true, shouldAddReadonly); if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { return parseClassStaticBlockDeclaration(pos, hasJSDoc, decorators, modifiers); } if (parseContextualModifier(SyntaxKind.GetKeyword)) { return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.GetAccessor, SignatureFlags.None); } if (parseContextualModifier(SyntaxKind.SetKeyword)) { return parseAccessorDeclaration(pos, hasJSDoc, decorators, modifiers, SyntaxKind.SetAccessor, SignatureFlags.None); } if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { const constructorDeclaration = tryParseConstructorDeclaration(pos, hasJSDoc, decorators, modifiers); if (constructorDeclaration) { return constructorDeclaration; } } if (isIndexSignature()) { return parseIndexSignatureDeclaration(pos, hasJSDoc, 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 (tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.StringLiteral || token() === SyntaxKind.NumericLiteral || token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBracketToken) { const isAmbient = some(modifiers, isDeclareModifier); if (isAmbient) { for (const m of modifiers!) { (m as Mutable).flags |= NodeFlags.Ambient; } return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers)); } else { return parsePropertyOrMethodDeclaration(pos, hasJSDoc, decorators, modifiers); } } if (decorators || modifiers) { // treat this as a property declaration with a missing name. const name = createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ true, Diagnostics.Declaration_expected); return parsePropertyDeclaration(pos, hasJSDoc, decorators, modifiers, name, /*questionToken*/ undefined); } // 'isClassMemberStart' should have hinted not to attempt parsing. return Debug.fail("Should not have attempted to parse class member declaration."); } function parseAnnotationElement(): AnnotationElement { const pos = getNodePos(); if (token() === SyntaxKind.SemicolonToken) { parseErrorAt(pos, pos, Diagnostics.Unexpected_keyword_or_identifier); } const hasJSDoc = hasPrecedingJSDocComment(); if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) { return createMissingNode(SyntaxKind.AnnotationPropertyDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Unexpected_keyword_or_identifier); } if (parseContextualModifier(SyntaxKind.GetKeyword)) { return createMissingNode(SyntaxKind.AnnotationPropertyDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Unexpected_keyword_or_identifier); } if (parseContextualModifier(SyntaxKind.SetKeyword)) { return createMissingNode(SyntaxKind.AnnotationPropertyDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Unexpected_keyword_or_identifier); } if (token() === SyntaxKind.ConstructorKeyword || token() === SyntaxKind.StringLiteral) { return createMissingNode(SyntaxKind.AnnotationPropertyDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Unexpected_keyword_or_identifier); } if (isIndexSignature()) { return createMissingNode(SyntaxKind.AnnotationPropertyDeclaration, /*reportAtCurrentPosition*/ true, Diagnostics.Unexpected_keyword_or_identifier); } if (tokenIsIdentifierOrKeyword(token())) { const name = parsePropertyName(); return parseAnnotationPropertyDeclaration(pos, hasJSDoc, name); } // 'isAnnotationMemberStart' should have hinted not to attempt parsing. return Debug.fail("Should not have attempted to parse annotation member declaration."); } function parseClassExpression(): ClassExpression { return parseClassDeclarationOrExpression(getNodePos(), hasPrecedingJSDocComment(), /*decorators*/ undefined, /*modifiers*/ undefined, SyntaxKind.ClassExpression) as ClassExpression; } function parseClassDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ClassDeclaration { return parseClassDeclarationOrExpression(pos, hasJSDoc, decorators, modifiers, SyntaxKind.ClassDeclaration) as ClassDeclaration; } function parseStructDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): StructDeclaration { return parseStructDeclarationOrExpression(pos, hasJSDoc, decorators, modifiers); } function parseClassDeclarationOrExpression(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, kind: ClassLikeDeclaration["kind"]): ClassLikeDeclaration { const savedAwaitContext = inAwaitContext(); parseExpected(SyntaxKind.ClassKeyword); // We don't parse the name here in await context, instead we will report a grammar error in the checker. const name = parseNameOfClassDeclarationOrExpression(); const typeParameters = parseTypeParameters(); if (some(modifiers, isExportModifier)) setAwaitContext(/*value*/ true); const heritageClauses = parseHeritageClauses(); let members; if (parseExpected(SyntaxKind.OpenBraceToken)) { // ClassTail[Yield,Await] : (Modified) See 14.5 // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } members = parseClassMembers(); parseExpected(SyntaxKind.CloseBraceToken); } else { members = createMissingList(); } setAwaitContext(savedAwaitContext); const node = kind === SyntaxKind.ClassDeclaration ? factory.createClassDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members) : factory.createClassExpression(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members); return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseStructDeclarationOrExpression(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): StructDeclaration { const savedAwaitContext = inAwaitContext(); parseExpected(SyntaxKind.StructKeyword); setStructContext(true); // We don't parse the name here in await context, instead we will report a grammar error in the checker. // struct Identifier logic is same to class Identifier const name = parseNameOfClassDeclarationOrExpression(); const typeParameters = parseTypeParameters(); if (some(modifiers, isExportModifier)) setAwaitContext(/*value*/ true); let heritageClauses = parseHeritageClauses(); const customComponent = sourceFileCompilerOptions.ets?.customComponent; if (!heritageClauses && customComponent) { heritageClauses = createVirtualHeritageClauses(customComponent); } let members; if (parseExpected(SyntaxKind.OpenBraceToken)) { // ClassTail[Yield,Await] : (Modified) See 14.5 // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } members = parseStructMembers(pos); parseExpected(SyntaxKind.CloseBraceToken); } else { members = createMissingList(); } setAwaitContext(savedAwaitContext); const node = factory.createStructDeclaration(combineDecoratorsAndModifiers(decorators, modifiers), name, typeParameters, heritageClauses, members); structStylesComponents.clear(); setStructContext(false); return withJSDoc(finishNode(node, pos), hasJSDoc); } function createVirtualHeritageClauses(customComponent: string): NodeArray { const curPos = getNodePos(); const clause = factory.createHeritageClause( SyntaxKind.ExtendsKeyword, createNodeArray([finishNode(factory.createExpressionWithTypeArguments( finishNode(factory.createIdentifier(/*text*/ customComponent), curPos, /*end*/ undefined, /*virtual*/ true), /*typeArguments*/ undefined ), curPos)], curPos, /*end*/ undefined, /*hasTrailingComma*/ false) ); return createNodeArray([finishNode(clause, curPos, /*end*/ undefined, /*virtual*/ true)], curPos, /*end*/ undefined, /*hasTrailingComma*/ false); } function parseNameOfClassDeclarationOrExpression(): Identifier | undefined { // implements is a future reserved word so // 'class implements' might mean either // - class expression with omitted name, 'implements' starts heritage clause // - class with name 'implements' // 'isImplementsClause' helps to disambiguate between these two cases return isBindingIdentifier() && !isImplementsClause() ? createIdentifier(isBindingIdentifier()) : undefined; } function isImplementsClause() { return token() === SyntaxKind.ImplementsKeyword && lookAhead(nextTokenIsIdentifierOrKeyword); } function parseHeritageClauses(): NodeArray | undefined { // ClassTail[Yield,Await] : (Modified) See 14.5 // ClassHeritage[?Yield,?Await]opt { ClassBody[?Yield,?Await]opt } if (isHeritageClause()) { return parseList(ParsingContext.HeritageClauses, parseHeritageClause); } return undefined; } function parseHeritageClause(): HeritageClause { const pos = getNodePos(); const tok = token(); Debug.assert(tok === SyntaxKind.ExtendsKeyword || tok === SyntaxKind.ImplementsKeyword); // isListElement() should ensure this. nextToken(); const types = parseDelimitedList(ParsingContext.HeritageClauseElement, parseExpressionWithTypeArguments); return finishNode(factory.createHeritageClause(tok, types), pos); } function parseExpressionWithTypeArguments(): ExpressionWithTypeArguments { const pos = getNodePos(); const expression = parseLeftHandSideExpressionOrHigher(); if (expression.kind === SyntaxKind.ExpressionWithTypeArguments) { return expression as ExpressionWithTypeArguments; } const typeArguments = tryParseTypeArguments(); return finishNode(factory.createExpressionWithTypeArguments(expression, typeArguments), pos); } function tryParseTypeArguments(): NodeArray | undefined { return token() === SyntaxKind.LessThanToken ? parseBracketedList(ParsingContext.TypeArguments, parseType, SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken) : undefined; } function isHeritageClause(): boolean { return token() === SyntaxKind.ExtendsKeyword || token() === SyntaxKind.ImplementsKeyword; } function parseClassMembers(): NodeArray { return parseList(ParsingContext.ClassMembers, parseClassElement); } function parseAnnotationMembers(): NodeArray { return parseList(ParsingContext.AnnotationMembers, parseAnnotationElement); } function parseStructMembers(pos: number): NodeArray { const structMembers = parseList(ParsingContext.ClassMembers, parseClassElement); const virtualStructMembers: ClassElement[] = []; // create constructor function argument object properties const virtualParameterProperties: TypeElement[] = []; structMembers.forEach(member => { virtualStructMembers.push(member); if (member.kind === SyntaxKind.PropertyDeclaration) { const property = member; virtualParameterProperties.push( finishVirtualNode( factory.createPropertySignature(getModifiers(property), property.name, factory.createToken(SyntaxKind.QuestionToken), property.type) ) ); } }); const parameters: ParameterDeclaration[] = []; if (virtualParameterProperties.length) { const type = finishVirtualNode(factory.createTypeLiteralNode(createNodeArray(virtualParameterProperties, 0, 0))); parameters.push( finishVirtualNode( factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotDotDotToken*/ undefined, finishVirtualNode(factory.createIdentifier("value")), factory.createToken(SyntaxKind.QuestionToken), type ) ) ); } // Add parameter ##storage?: LocalStorage to struct constructor. parameters.push( finishVirtualNode( factory.createParameterDeclaration(/*modifiers*/ undefined, /*dotDotDotToken*/ undefined, finishVirtualNode(factory.createIdentifier("##storage")), factory.createToken(SyntaxKind.QuestionToken), finishVirtualNode(factory.createTypeReferenceNode(finishVirtualNode(factory.createIdentifier("LocalStorage")))) ) ) ); const emptyBody = finishVirtualNode(factory.createBlock(createNodeArray([], 0, 0))); const virtualConstructor = factory.createConstructorDeclaration(/*modifier*/ undefined, createNodeArray(parameters, 0, 0), emptyBody); virtualStructMembers.unshift(finishVirtualNode(virtualConstructor, pos, pos)); return createNodeArray(virtualStructMembers, structMembers.pos); } function finishVirtualNode(node: T, start = 0, end = 0) { return finishNode(node, start, end, /*virtual*/ true); } function parseInterfaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): InterfaceDeclaration { parseExpected(SyntaxKind.InterfaceKeyword); const name = parseIdentifier(); const typeParameters = parseTypeParameters(); const heritageClauses = parseHeritageClauses(); const members = parseObjectTypeMembers(); const node = factory.createInterfaceDeclaration(modifiers, name, typeParameters, heritageClauses, members); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseTypeAliasDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): TypeAliasDeclaration { parseExpected(SyntaxKind.TypeKeyword); const name = parseIdentifier(); const typeParameters = parseTypeParameters(); parseExpected(SyntaxKind.EqualsToken); const type = token() === SyntaxKind.IntrinsicKeyword && tryParse(parseKeywordAndNoDot) || parseType(); parseSemicolon(); const node = factory.createTypeAliasDeclaration(modifiers, name, typeParameters, type); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } // 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 { const pos = getNodePos(); const hasJSDoc = hasPrecedingJSDocComment(); const name = parsePropertyName(); const initializer = allowInAnd(parseInitializer); return withJSDoc(finishNode(factory.createEnumMember(name, initializer), pos), hasJSDoc); } function parseEnumDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): EnumDeclaration { parseExpected(SyntaxKind.EnumKeyword); const name = parseIdentifier(); let members; if (parseExpected(SyntaxKind.OpenBraceToken)) { members = doOutsideOfYieldAndAwaitContext(() => parseDelimitedList(ParsingContext.EnumMembers, parseEnumMember)); parseExpected(SyntaxKind.CloseBraceToken); } else { members = createMissingList(); } const node = factory.createEnumDeclaration(modifiers, name, members); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseModuleBlock(): ModuleBlock { const pos = getNodePos(); let statements; if (parseExpected(SyntaxKind.OpenBraceToken)) { statements = parseList(ParsingContext.BlockStatements, parseStatement); parseExpected(SyntaxKind.CloseBraceToken); } else { statements = createMissingList(); } return finishNode(factory.createModuleBlock(statements), pos); } function parseModuleOrNamespaceDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, flags: NodeFlags): ModuleDeclaration { // If we are parsing a dotted namespace name, we want to // propagate the 'Namespace' flag across the names if set. const namespaceFlag = flags & NodeFlags.Namespace; const name = parseIdentifier(); const body = parseOptional(SyntaxKind.DotToken) ? parseModuleOrNamespaceDeclaration(getNodePos(), /*hasJSDoc*/ false, /*decorators*/ undefined, /*modifiers*/ undefined, NodeFlags.NestedNamespace | namespaceFlag) as NamespaceDeclaration : parseModuleBlock(); const node = factory.createModuleDeclaration(modifiers, name, body, flags); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseAmbientExternalModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { let flags: NodeFlags = 0; let name; if (token() === SyntaxKind.GlobalKeyword) { // parse 'global' as name of global scope augmentation name = parseIdentifier(); flags |= NodeFlags.GlobalAugmentation; } else { name = parseLiteralNode() as StringLiteral; name.text = internIdentifier(name.text); } let body: ModuleBlock | undefined; if (token() === SyntaxKind.OpenBraceToken) { body = parseModuleBlock(); } else { parseSemicolon(); } const node = factory.createModuleDeclaration(modifiers, name, body, flags); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseModuleDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ModuleDeclaration { let flags: NodeFlags = 0; if (token() === SyntaxKind.GlobalKeyword) { // global augmentation return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); } else if (parseOptional(SyntaxKind.NamespaceKeyword)) { flags |= NodeFlags.Namespace; } else { parseExpected(SyntaxKind.ModuleKeyword); if (token() === SyntaxKind.StringLiteral) { return parseAmbientExternalModuleDeclaration(pos, hasJSDoc, decorators, modifiers); } } return parseModuleOrNamespaceDeclaration(pos, hasJSDoc, decorators, modifiers, flags); } function isExternalModuleReference() { return token() === SyntaxKind.RequireKeyword && lookAhead(nextTokenIsOpenParen); } function nextTokenIsOpenParen() { return nextToken() === SyntaxKind.OpenParenToken; } function nextTokenIsOpenBrace() { return nextToken() === SyntaxKind.OpenBraceToken; } function nextTokenIsSlash() { return nextToken() === SyntaxKind.SlashToken; } function parseNamespaceExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): NamespaceExportDeclaration { parseExpected(SyntaxKind.AsKeyword); parseExpected(SyntaxKind.NamespaceKeyword); const name = parseIdentifier(); parseSemicolon(); const node = factory.createNamespaceExportDeclaration(name); // NamespaceExportDeclaration nodes cannot have decorators or modifiers, so we attach them here so we can report them in the grammar checker (node as Mutable).illegalDecorators = decorators; (node as Mutable).modifiers = modifiers; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseImportDeclarationOrImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ImportEqualsDeclaration | ImportDeclaration { parseExpected(SyntaxKind.ImportKeyword); const afterImportPos = scanner.getStartPos(); // We don't parse the identifier here in await context, instead we will report a grammar error in the checker. let identifier: Identifier | undefined; if (isIdentifier()) { identifier = parseIdentifier(); } let isTypeOnly = false; let isLazy = false; if (token() !== SyntaxKind.FromKeyword && identifier?.escapedText === "type" && (isIdentifier() || tokenAfterImportDefinitelyProducesImportDeclaration()) ) { isTypeOnly = true; identifier = isIdentifier() ? parseIdentifier() : undefined; } else if (isSetLazy(identifier)) { isLazy = true; identifier = isIdentifier() ? parseIdentifier() : undefined; } if (identifier && !tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration()) { return parseImportEqualsDeclaration(pos, hasJSDoc, decorators, modifiers, identifier, isTypeOnly); } // ImportDeclaration: // import ImportClause from ModuleSpecifier; // import ModuleSpecifier; let importClause: ImportClause | undefined; if (identifier || // import id token() === SyntaxKind.AsteriskToken || // import * token() === SyntaxKind.OpenBraceToken // import { ) { importClause = parseImportClause(identifier, afterImportPos, isTypeOnly); (importClause as Mutable).isLazy = isLazy; parseExpected(SyntaxKind.FromKeyword); } const moduleSpecifier = parseModuleSpecifier(); let assertClause: AssertClause | undefined; if (token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { assertClause = parseAssertClause(); } parseSemicolon(); const node = factory.createImportDeclaration(modifiers, importClause, moduleSpecifier, assertClause); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function isSetLazy(identifier: Identifier | undefined): boolean { // 1. import lazy { export } from "mod"; // 2. import lazy defaultExport from "mod"; // 3. import lazy defaultExport, { export, /* ... */ } from "mod"; return identifier?.escapedText === 'lazy' && (token() === SyntaxKind.OpenBraceToken || (isIdentifier() && lookAhead(() => nextToken() === SyntaxKind.FromKeyword)) || (isIdentifier() && lookAhead(() => nextToken() === SyntaxKind.CommaToken && nextToken() === SyntaxKind.OpenBraceToken))); } function parseAssertEntry() { const pos = getNodePos(); const name = tokenIsIdentifierOrKeyword(token()) ? parseIdentifierName() : parseLiteralLikeNode(SyntaxKind.StringLiteral) as StringLiteral; parseExpected(SyntaxKind.ColonToken); const value = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); return finishNode(factory.createAssertEntry(name, value), pos); } function parseAssertClause(skipAssertKeyword?: true) { const pos = getNodePos(); if (!skipAssertKeyword) { parseExpected(SyntaxKind.AssertKeyword); } const openBracePosition = scanner.getTokenPos(); if (parseExpected(SyntaxKind.OpenBraceToken)) { const multiLine = scanner.hasPrecedingLineBreak(); const elements = parseDelimitedList(ParsingContext.AssertEntries, parseAssertEntry, /*considerSemicolonAsDelimiter*/ true); if (!parseExpected(SyntaxKind.CloseBraceToken)) { const lastError = lastOrUndefined(parseDiagnostics); if (lastError && lastError.code === Diagnostics._0_expected.code) { addRelatedInfo( lastError, createDetachedDiagnostic(fileName, openBracePosition, 1, Diagnostics.The_parser_expected_to_find_a_1_to_match_the_0_token_here, "{", "}") ); } } return finishNode(factory.createAssertClause(elements, multiLine), pos); } else { const elements = createNodeArray([], getNodePos(), /*end*/ undefined, /*hasTrailingComma*/ false); return finishNode(factory.createAssertClause(elements, /*multiLine*/ false), pos); } } function tokenAfterImportDefinitelyProducesImportDeclaration() { return token() === SyntaxKind.AsteriskToken || token() === SyntaxKind.OpenBraceToken; } function tokenAfterImportedIdentifierDefinitelyProducesImportDeclaration() { // In `import id ___`, the current token decides whether to produce // an ImportDeclaration or ImportEqualsDeclaration. return token() === SyntaxKind.CommaToken || token() === SyntaxKind.FromKeyword; } function parseImportEqualsDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined, identifier: Identifier, isTypeOnly: boolean): ImportEqualsDeclaration { parseExpected(SyntaxKind.EqualsToken); const moduleReference = parseModuleReference(); parseSemicolon(); const node = factory.createImportEqualsDeclaration(modifiers, isTypeOnly, identifier, moduleReference); (node as Mutable).illegalDecorators = decorators; const finished = withJSDoc(finishNode(node, pos), hasJSDoc); return finished; } function parseImportClause(identifier: Identifier | undefined, pos: number, isTypeOnly: boolean) { // ImportClause: // ImportedDefaultBinding // NameSpaceImport // NamedImports // ImportedDefaultBinding, NameSpaceImport // ImportedDefaultBinding, NamedImports // If there was no default import or if there is comma token after default import // parse namespace or named imports let namedBindings: NamespaceImport | NamedImports | undefined; if (!identifier || parseOptional(SyntaxKind.CommaToken)) { namedBindings = token() === SyntaxKind.AsteriskToken ? parseNamespaceImport() : parseNamedImportsOrExports(SyntaxKind.NamedImports); } return finishNode(factory.createImportClause(isTypeOnly, identifier, namedBindings), pos); } function parseModuleReference() { return isExternalModuleReference() ? parseExternalModuleReference() : parseEntityName(/*allowReservedWords*/ false); } function parseExternalModuleReference() { const pos = getNodePos(); parseExpected(SyntaxKind.RequireKeyword); parseExpected(SyntaxKind.OpenParenToken); const expression = parseModuleSpecifier(); parseExpected(SyntaxKind.CloseParenToken); return finishNode(factory.createExternalModuleReference(expression), pos); } function parseModuleSpecifier(): Expression { if (token() === SyntaxKind.StringLiteral) { const result = parseLiteralNode(); result.text = internIdentifier(result.text); return result; } else { // 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 // check pass. return parseExpression(); } } function parseNamespaceImport(): NamespaceImport { // NameSpaceImport: // * as ImportedBinding const pos = getNodePos(); parseExpected(SyntaxKind.AsteriskToken); parseExpected(SyntaxKind.AsKeyword); const name = parseIdentifier(); return finishNode(factory.createNamespaceImport(name), pos); } function parseNamedImportsOrExports(kind: SyntaxKind.NamedImports): NamedImports; function parseNamedImportsOrExports(kind: SyntaxKind.NamedExports): NamedExports; function parseNamedImportsOrExports(kind: SyntaxKind): NamedImportsOrExports { const pos = getNodePos(); // NamedImports: // { } // { ImportsList } // { ImportsList, } // ImportsList: // ImportSpecifier // ImportsList, ImportSpecifier const node = kind === SyntaxKind.NamedImports ? factory.createNamedImports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseImportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)) : factory.createNamedExports(parseBracketedList(ParsingContext.ImportOrExportSpecifiers, parseExportSpecifier, SyntaxKind.OpenBraceToken, SyntaxKind.CloseBraceToken)); return finishNode(node, pos); } function parseExportSpecifier() { const hasJSDoc = hasPrecedingJSDocComment(); return withJSDoc(parseImportOrExportSpecifier(SyntaxKind.ExportSpecifier) as ExportSpecifier, hasJSDoc); } function parseImportSpecifier() { return parseImportOrExportSpecifier(SyntaxKind.ImportSpecifier) as ImportSpecifier; } function parseImportOrExportSpecifier(kind: SyntaxKind): ImportOrExportSpecifier { const pos = getNodePos(); // ImportSpecifier: // BindingIdentifier // IdentifierName as BindingIdentifier // ExportSpecifier: // IdentifierName // IdentifierName as IdentifierName let checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); let checkIdentifierStart = scanner.getTokenPos(); let checkIdentifierEnd = scanner.getTextPos(); let isTypeOnly = false; let propertyName: Identifier | undefined; let canParseAsKeyword = true; let name = parseIdentifierName(); if (name.escapedText === "type") { // If the first token of an import specifier is 'type', there are a lot of possibilities, // especially if we see 'as' afterwards: // // import { type } from "mod"; - isTypeOnly: false, name: type // import { type as } from "mod"; - isTypeOnly: true, name: as // import { type as as } from "mod"; - isTypeOnly: false, name: as, propertyName: type // import { type as as as } from "mod"; - isTypeOnly: true, name: as, propertyName: as if (token() === SyntaxKind.AsKeyword) { // { type as ...? } const firstAs = parseIdentifierName(); if (token() === SyntaxKind.AsKeyword) { // { type as as ...? } const secondAs = parseIdentifierName(); if (tokenIsIdentifierOrKeyword(token())) { // { type as as something } isTypeOnly = true; propertyName = firstAs; name = parseNameWithKeywordCheck(); canParseAsKeyword = false; } else { // { type as as } propertyName = name; name = secondAs; canParseAsKeyword = false; } } else if (tokenIsIdentifierOrKeyword(token())) { // { type as something } propertyName = name; canParseAsKeyword = false; name = parseNameWithKeywordCheck(); } else { // { type as } isTypeOnly = true; name = firstAs; } } else if (tokenIsIdentifierOrKeyword(token())) { // { type something ...? } isTypeOnly = true; name = parseNameWithKeywordCheck(); } } if (canParseAsKeyword && token() === SyntaxKind.AsKeyword) { propertyName = name; parseExpected(SyntaxKind.AsKeyword); name = parseNameWithKeywordCheck(); } if (kind === SyntaxKind.ImportSpecifier && checkIdentifierIsKeyword) { parseErrorAt(checkIdentifierStart, checkIdentifierEnd, Diagnostics.Identifier_expected); } const node = kind === SyntaxKind.ImportSpecifier ? factory.createImportSpecifier(isTypeOnly, propertyName, name) : factory.createExportSpecifier(isTypeOnly, propertyName, name); return finishNode(node, pos); function parseNameWithKeywordCheck() { checkIdentifierIsKeyword = isKeyword(token()) && !isIdentifier(); checkIdentifierStart = scanner.getTokenPos(); checkIdentifierEnd = scanner.getTextPos(); return parseIdentifierName(); } } function parseNamespaceExport(pos: number): NamespaceExport { return finishNode(factory.createNamespaceExport(parseIdentifierName()), pos); } function parseExportDeclaration(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportDeclaration { const savedAwaitContext = inAwaitContext(); setAwaitContext(/*value*/ true); let exportClause: NamedExportBindings | undefined; let moduleSpecifier: Expression | undefined; let assertClause: AssertClause | undefined; const isTypeOnly = parseOptional(SyntaxKind.TypeKeyword); const namespaceExportPos = getNodePos(); if (parseOptional(SyntaxKind.AsteriskToken)) { if (parseOptional(SyntaxKind.AsKeyword)) { exportClause = parseNamespaceExport(namespaceExportPos); } parseExpected(SyntaxKind.FromKeyword); moduleSpecifier = parseModuleSpecifier(); } else { exportClause = parseNamedImportsOrExports(SyntaxKind.NamedExports); // It is not uncommon to accidentally omit the 'from' keyword. Additionally, in editing scenarios, // the 'from' keyword can be parsed as a named export when the export clause is unterminated (i.e. `export { from "moduleName";`) // If we don't have a 'from' keyword, see if we have a string literal such that ASI won't take effect. if (token() === SyntaxKind.FromKeyword || (token() === SyntaxKind.StringLiteral && !scanner.hasPrecedingLineBreak())) { parseExpected(SyntaxKind.FromKeyword); moduleSpecifier = parseModuleSpecifier(); } } if (moduleSpecifier && token() === SyntaxKind.AssertKeyword && !scanner.hasPrecedingLineBreak()) { assertClause = parseAssertClause(); } parseSemicolon(); setAwaitContext(savedAwaitContext); const node = factory.createExportDeclaration(modifiers, isTypeOnly, exportClause, moduleSpecifier, assertClause); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } function parseExportAssignment(pos: number, hasJSDoc: boolean, decorators: NodeArray | undefined, modifiers: NodeArray | undefined): ExportAssignment { const savedAwaitContext = inAwaitContext(); setAwaitContext(/*value*/ true); let isExportEquals: boolean | undefined; if (parseOptional(SyntaxKind.EqualsToken)) { isExportEquals = true; } else { parseExpected(SyntaxKind.DefaultKeyword); } const expression = parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); parseSemicolon(); setAwaitContext(savedAwaitContext); const node = factory.createExportAssignment(modifiers, isExportEquals, expression); (node as Mutable).illegalDecorators = decorators; return withJSDoc(finishNode(node, pos), hasJSDoc); } const enum ParsingContext { SourceElements, // Elements in source file 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 AnnotationMembers, // Members in annotation 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 JsxAttributes, // Attributes in jsx element JsxChildren, // Things between opening and closing JSX tags ArrayLiteralMembers, // Members in array literal Parameters, // Parameters in parameter list JSDocParameters, // JSDoc parameters in parameter list of JSDoc function type RestProperties, // Property names in a rest type 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, AssertEntries, // Import entries list. Count // Number of parsing contexts } const enum Tristate { False, True, Unknown } export namespace JSDocParser { export function parseJSDocTypeExpressionForTests(content: string, start: number | undefined, length: number | undefined): { jsDocTypeExpression: JSDocTypeExpression, diagnostics: Diagnostic[] } | undefined { initializeState("file.js", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); scanner.setText(content, start, length); currentToken = scanner.scan(); const jsDocTypeExpression = parseJSDocTypeExpression(); const sourceFile = createSourceFile("file.js", ScriptTarget.Latest, ScriptKind.JS, /*isDeclarationFile*/ false, [], factory.createToken(SyntaxKind.EndOfFileToken), NodeFlags.None, noop); const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); if (jsDocDiagnostics) { sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile); } clearState(); return jsDocTypeExpression ? { jsDocTypeExpression, diagnostics } : undefined; } // Parses out a JSDoc type expression. export function parseJSDocTypeExpression(mayOmitBraces?: boolean): JSDocTypeExpression { const pos = getNodePos(); const hasBrace = (mayOmitBraces ? parseOptional : parseExpected)(SyntaxKind.OpenBraceToken); const type = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); if (!mayOmitBraces || hasBrace) { parseExpectedJSDoc(SyntaxKind.CloseBraceToken); } const result = factory.createJSDocTypeExpression(type); fixupParentReferences(result); return finishNode(result, pos); } export function parseJSDocNameReference(): JSDocNameReference { const pos = getNodePos(); const hasBrace = parseOptional(SyntaxKind.OpenBraceToken); const p2 = getNodePos(); let entityName: EntityName | JSDocMemberName = parseEntityName(/* allowReservedWords*/ false); while (token() === SyntaxKind.PrivateIdentifier) { reScanHashToken(); // rescan #id as # id nextTokenJSDoc(); // then skip the # entityName = finishNode(factory.createJSDocMemberName(entityName, parseIdentifier()), p2); } if (hasBrace) { parseExpectedJSDoc(SyntaxKind.CloseBraceToken); } const result = factory.createJSDocNameReference(entityName); fixupParentReferences(result); return finishNode(result, pos); } export function parseIsolatedJSDocComment(content: string, start: number | undefined, length: number | undefined): { jsDoc: JSDoc, diagnostics: Diagnostic[] } | undefined { initializeState("", content, ScriptTarget.Latest, /*_syntaxCursor:*/ undefined, ScriptKind.JS); const jsDoc = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); const sourceFile = { languageVariant: LanguageVariant.Standard, text: content } as SourceFile; const diagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile); clearState(); return jsDoc ? { jsDoc, diagnostics } : undefined; } export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined { const saveToken = currentToken; const saveParseDiagnosticsLength = parseDiagnostics.length; const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(start, length)); setParent(comment, parent); if (contextFlags & NodeFlags.JavaScriptFile) { if (!jsDocDiagnostics) { jsDocDiagnostics = []; } jsDocDiagnostics.push(...parseDiagnostics); } currentToken = saveToken; parseDiagnostics.length = saveParseDiagnosticsLength; parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; return comment; } const enum JSDocState { BeginningOfLine, SawAsterisk, SavingComments, SavingBackticks, // NOTE: Only used when parsing tag comments } const enum PropertyLikeParse { Property = 1 << 0, Parameter = 1 << 1, CallbackParameter = 1 << 2, } function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined { const content = sourceText; const end = length === undefined ? content.length : start + length; length = end - start; Debug.assert(start >= 0); Debug.assert(start <= end); Debug.assert(end <= content.length); // Check for /** (JSDoc opening part) if (!isJSDocLikeText(content, start)) { return undefined; } let tags: JSDocTag[]; let tagsPos: number; let tagsEnd: number; let linkEnd: number; let commentsPos: number | undefined; let comments: string[] = []; const parts: JSDocComment[] = []; // + 3 for leading /**, - 5 in total for /** */ return scanner.scanRange(start + 3, length - 5, () => { // Initially we can parse out a tag. We also have seen a starting asterisk. // This is so that /** * @type */ doesn't parse. let state = JSDocState.SawAsterisk; let margin: number | undefined; // + 4 for leading '/** ' // + 1 because the last index of \n is always one index before the first character in the line and coincidentally, if there is no \n before start, it is -1, which is also one index before the first character let indent = start - (content.lastIndexOf("\n", start) + 1) + 4; function pushComment(text: string) { if (!margin) { margin = indent; } comments.push(text); indent += text.length; } nextTokenJSDoc(); while (parseOptionalJsdoc(SyntaxKind.WhitespaceTrivia)); if (parseOptionalJsdoc(SyntaxKind.NewLineTrivia)) { state = JSDocState.BeginningOfLine; indent = 0; } loop: while (true) { switch (token()) { case SyntaxKind.AtToken: if (state === JSDocState.BeginningOfLine || state === JSDocState.SawAsterisk) { removeTrailingWhitespace(comments); if (!commentsPos) commentsPos = getNodePos(); addTag(parseTag(indent)); // NOTE: According to usejsdoc.org, a tag goes to end of line, except the last tag. // Real-world comments may break this rule, so "BeginningOfLine" will not be a real line beginning // for malformed examples like `/** @param {string} x @returns {number} the length */` state = JSDocState.BeginningOfLine; margin = undefined; } else { pushComment(scanner.getTokenText()); } break; case SyntaxKind.NewLineTrivia: comments.push(scanner.getTokenText()); state = JSDocState.BeginningOfLine; indent = 0; break; case SyntaxKind.AsteriskToken: const asterisk = scanner.getTokenText(); if (state === JSDocState.SawAsterisk || state === JSDocState.SavingComments) { // If we've already seen an asterisk, then we can no longer parse a tag on this line state = JSDocState.SavingComments; pushComment(asterisk); } else { // Ignore the first asterisk on a line state = JSDocState.SawAsterisk; indent += asterisk.length; } break; case SyntaxKind.WhitespaceTrivia: // only collect whitespace if we're already saving comments or have just crossed the comment indent margin const whitespace = scanner.getTokenText(); if (state === JSDocState.SavingComments) { comments.push(whitespace); } else if (margin !== undefined && indent + whitespace.length > margin) { comments.push(whitespace.slice(margin - indent)); } indent += whitespace.length; break; case SyntaxKind.EndOfFileToken: break loop; case SyntaxKind.OpenBraceToken: state = JSDocState.SavingComments; const commentEnd = scanner.getStartPos(); const linkStart = scanner.getTextPos() - 1; const link = parseJSDocLink(linkStart); if (link) { if (!linkEnd) { removeLeadingNewlines(comments); } parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentEnd)); parts.push(link); comments = []; linkEnd = scanner.getTextPos(); break; } // fallthrough if it's not a {@link sequence default: // Anything else is doc comment text. We just save it. Because it // wasn't a tag, we can no longer parse a tag on this line until we hit the next // line break. state = JSDocState.SavingComments; pushComment(scanner.getTokenText()); break; } nextTokenJSDoc(); } removeTrailingWhitespace(comments); if (parts.length && comments.length) { parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? start, commentsPos)); } if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set"); const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd); return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : comments.length ? comments.join("") : undefined, tagsArray), start, end); }); function removeLeadingNewlines(comments: string[]) { while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) { comments.shift(); } } function removeTrailingWhitespace(comments: string[]) { while (comments.length && comments[comments.length - 1].trim() === "") { comments.pop(); } } function isNextNonwhitespaceTokenEndOfFile(): boolean { // We must use infinite lookahead, as there could be any number of newlines :( while (true) { nextTokenJSDoc(); if (token() === SyntaxKind.EndOfFileToken) { return true; } if (!(token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia)) { return false; } } } function skipWhitespace(): void { if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } } while (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { nextTokenJSDoc(); } } function skipWhitespaceOrAsterisk(): string { if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) { return ""; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range } } let precedingLineBreak = scanner.hasPrecedingLineBreak(); let seenLineBreak = false; let indentText = ""; while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) { indentText += scanner.getTokenText(); if (token() === SyntaxKind.NewLineTrivia) { precedingLineBreak = true; seenLineBreak = true; indentText = ""; } else if (token() === SyntaxKind.AsteriskToken) { precedingLineBreak = false; } nextTokenJSDoc(); } return seenLineBreak ? indentText : ""; } function parseTag(margin: number) { Debug.assert(token() === SyntaxKind.AtToken); const start = scanner.getTokenPos(); nextTokenJSDoc(); const tagName = parseJSDocIdentifierName(/*message*/ undefined); const indentText = skipWhitespaceOrAsterisk(); let tag: JSDocTag | undefined; switch (tagName.escapedText) { case "author": tag = parseAuthorTag(start, tagName, margin, indentText); break; case "implements": tag = parseImplementsTag(start, tagName, margin, indentText); break; case "augments": case "extends": tag = parseAugmentsTag(start, tagName, margin, indentText); break; case "class": case "constructor": tag = parseSimpleTag(start, factory.createJSDocClassTag, tagName, margin, indentText); break; case "public": tag = parseSimpleTag(start, factory.createJSDocPublicTag, tagName, margin, indentText); break; case "private": tag = parseSimpleTag(start, factory.createJSDocPrivateTag, tagName, margin, indentText); break; case "protected": tag = parseSimpleTag(start, factory.createJSDocProtectedTag, tagName, margin, indentText); break; case "readonly": tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText); break; case "override": tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText); break; case "deprecated": hasDeprecatedTag = true; tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText); break; case "this": tag = parseThisTag(start, tagName, margin, indentText); break; case "enum": tag = parseEnumTag(start, tagName, margin, indentText); break; case "arg": case "argument": case "param": return parseParameterOrPropertyTag(start, tagName, PropertyLikeParse.Parameter, margin); case "return": case "returns": tag = parseReturnTag(start, tagName, margin, indentText); break; case "template": tag = parseTemplateTag(start, tagName, margin, indentText); break; case "type": tag = parseTypeTag(start, tagName, margin, indentText); break; case "typedef": tag = parseTypedefTag(start, tagName, margin, indentText); break; case "callback": tag = parseCallbackTag(start, tagName, margin, indentText); break; case "see": tag = parseSeeTag(start, tagName, margin, indentText); break; default: tag = parseUnknownTag(start, tagName, margin, indentText); break; } return tag; } function parseTrailingTagComments(pos: number, end: number, margin: number, indentText: string) { // some tags, like typedef and callback, have already parsed their comments earlier if (!indentText) { margin += end - pos; } return parseTagComments(margin, indentText.slice(margin)); } function parseTagComments(indent: number, initialMargin?: string): string | NodeArray | undefined { const commentsPos = getNodePos(); let comments: string[] = []; const parts: JSDocComment[] = []; let linkEnd; let state = JSDocState.BeginningOfLine; let previousWhitespace = true; let margin: number | undefined; function pushComment(text: string) { if (!margin) { margin = indent; } comments.push(text); indent += text.length; } if (initialMargin !== undefined) { // jump straight to saving comments if there is some initial indentation if (initialMargin !== "") { pushComment(initialMargin); } state = JSDocState.SawAsterisk; } let tok = token() as JSDocSyntaxKind; loop: while (true) { switch (tok) { case SyntaxKind.NewLineTrivia: state = JSDocState.BeginningOfLine; // don't use pushComment here because we want to keep the margin unchanged comments.push(scanner.getTokenText()); indent = 0; break; case SyntaxKind.AtToken: if (state === JSDocState.SavingBackticks || state === JSDocState.SavingComments && (!previousWhitespace || lookAhead(isNextJSDocTokenWhitespace))) { // @ doesn't start a new tag inside ``, and inside a comment, only after whitespace or not before whitespace comments.push(scanner.getTokenText()); break; } scanner.setTextPos(scanner.getTextPos() - 1); // falls through case SyntaxKind.EndOfFileToken: // Done break loop; case SyntaxKind.WhitespaceTrivia: if (state === JSDocState.SavingComments || state === JSDocState.SavingBackticks) { pushComment(scanner.getTokenText()); } else { const whitespace = scanner.getTokenText(); // if the whitespace crosses the margin, take only the whitespace that passes the margin if (margin !== undefined && indent + whitespace.length > margin) { comments.push(whitespace.slice(margin - indent)); } indent += whitespace.length; } break; case SyntaxKind.OpenBraceToken: state = JSDocState.SavingComments; const commentEnd = scanner.getStartPos(); const linkStart = scanner.getTextPos() - 1; const link = parseJSDocLink(linkStart); if (link) { parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos, commentEnd)); parts.push(link); comments = []; linkEnd = scanner.getTextPos(); } else { pushComment(scanner.getTokenText()); } break; case SyntaxKind.BacktickToken: if (state === JSDocState.SavingBackticks) { state = JSDocState.SavingComments; } else { state = JSDocState.SavingBackticks; } pushComment(scanner.getTokenText()); break; case SyntaxKind.AsteriskToken: if (state === JSDocState.BeginningOfLine) { // leading asterisks start recording on the *next* (non-whitespace) token state = JSDocState.SawAsterisk; indent += 1; break; } // record the * as a comment // falls through default: if (state !== JSDocState.SavingBackticks) { state = JSDocState.SavingComments; // leading identifiers start recording as well } pushComment(scanner.getTokenText()); break; } previousWhitespace = token() === SyntaxKind.WhitespaceTrivia; tok = nextTokenJSDoc(); } removeLeadingNewlines(comments); removeTrailingWhitespace(comments); if (parts.length) { if (comments.length) { parts.push(finishNode(factory.createJSDocText(comments.join("")), linkEnd ?? commentsPos)); } return createNodeArray(parts, commentsPos, scanner.getTextPos()); } else if (comments.length) { return comments.join(""); } } function isNextJSDocTokenWhitespace() { const next = nextTokenJSDoc(); return next === SyntaxKind.WhitespaceTrivia || next === SyntaxKind.NewLineTrivia; } function parseJSDocLink(start: number) { const linkType = tryParse(parseJSDocLinkPrefix); if (!linkType) { return undefined; } nextTokenJSDoc(); // start at token after link, then skip any whitespace skipWhitespace(); // parseEntityName logs an error for non-identifier, so create a MissingNode ourselves to avoid the error const p2 = getNodePos(); let name: EntityName | JSDocMemberName | undefined = tokenIsIdentifierOrKeyword(token()) ? parseEntityName(/*allowReservedWords*/ true) : undefined; if (name) { while (token() === SyntaxKind.PrivateIdentifier) { reScanHashToken(); // rescan #id as # id nextTokenJSDoc(); // then skip the # name = finishNode(factory.createJSDocMemberName(name, parseIdentifier()), p2); } } const text = []; while (token() !== SyntaxKind.CloseBraceToken && token() !== SyntaxKind.NewLineTrivia && token() !== SyntaxKind.EndOfFileToken) { text.push(scanner.getTokenText()); nextTokenJSDoc(); } const create = linkType === "link" ? factory.createJSDocLink : linkType === "linkcode" ? factory.createJSDocLinkCode : factory.createJSDocLinkPlain; return finishNode(create(name, text.join("")), start, scanner.getTextPos()); } function parseJSDocLinkPrefix() { skipWhitespaceOrAsterisk(); if (token() === SyntaxKind.OpenBraceToken && nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc())) { const kind = scanner.getTokenValue(); if (isJSDocLinkTag(kind)) return kind; } } function isJSDocLinkTag(kind: string) { return kind === "link" || kind === "linkcode" || kind === "linkplain"; } function parseUnknownTag(start: number, tagName: Identifier, indent: number, indentText: string) { return finishNode(factory.createJSDocUnknownTag(tagName, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); } function addTag(tag: JSDocTag | undefined): void { if (!tag) { return; } if (!tags) { tags = [tag]; tagsPos = tag.pos; } else { tags.push(tag); } tagsEnd = tag.end; } function tryParseTypeExpression(): JSDocTypeExpression | undefined { skipWhitespaceOrAsterisk(); return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; } function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { // Looking for something like '[foo]', 'foo', '[foo.bar]' or 'foo.bar' const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); if (isBracketed) { skipWhitespace(); } // a markdown-quoted name: `arg` is not legal jsdoc, but occurs in the wild const isBackquoted = parseOptionalJsdoc(SyntaxKind.BacktickToken); const name = parseJSDocEntityName(); if (isBackquoted) { parseExpectedTokenJSDoc(SyntaxKind.BacktickToken); } if (isBracketed) { skipWhitespace(); // May have an optional default, e.g. '[foo = 42]' if (parseOptionalToken(SyntaxKind.EqualsToken)) { parseExpression(); } parseExpected(SyntaxKind.CloseBracketToken); } return { name, isBracketed }; } function isObjectOrObjectArrayTypeReference(node: TypeNode): boolean { switch (node.kind) { case SyntaxKind.ObjectKeyword: return true; case SyntaxKind.ArrayType: return isObjectOrObjectArrayTypeReference((node as ArrayTypeNode).elementType); default: return isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) && node.typeName.escapedText === "Object" && !node.typeArguments; } } function parseParameterOrPropertyTag(start: number, tagName: Identifier, target: PropertyLikeParse, indent: number): JSDocParameterTag | JSDocPropertyTag { let typeExpression = tryParseTypeExpression(); let isNameFirst = !typeExpression; skipWhitespaceOrAsterisk(); const { name, isBracketed } = parseBracketNameInPropertyAndParamTag(); const indentText = skipWhitespaceOrAsterisk(); if (isNameFirst && !lookAhead(parseJSDocLinkPrefix)) { typeExpression = tryParseTypeExpression(); } const comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); const nestedTypeLiteral = target !== PropertyLikeParse.CallbackParameter && parseNestedTypeLiteral(typeExpression, name, target, indent); if (nestedTypeLiteral) { typeExpression = nestedTypeLiteral; isNameFirst = true; } const result = target === PropertyLikeParse.Property ? factory.createJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment) : factory.createJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment); return finishNode(result, start); } function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) { if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) { const pos = getNodePos(); let child: JSDocPropertyLikeTag | JSDocTypeTag | false; let children: JSDocPropertyLikeTag[] | undefined; while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) { if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) { children = append(children, child); } } if (children) { const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos); return finishNode(factory.createJSDocTypeExpression(literal), pos); } } } function parseReturnTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocReturnTag { if (some(tags, isJSDocReturnTag)) { parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } const typeExpression = tryParseTypeExpression(); return finishNode(factory.createJSDocReturnTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); } function parseTypeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocTypeTag { if (some(tags, isJSDocTypeTag)) { parseErrorAt(tagName.pos, scanner.getTokenPos(), Diagnostics._0_tag_already_specified, tagName.escapedText); } const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; return finishNode(factory.createJSDocTypeTag(tagName, typeExpression, comments), start); } function parseSeeTag(start: number, tagName: Identifier, indent?: number, indentText?: string): JSDocSeeTag { const isMarkdownOrJSDocLink = token() === SyntaxKind.OpenBracketToken || lookAhead(() => nextTokenJSDoc() === SyntaxKind.AtToken && tokenIsIdentifierOrKeyword(nextTokenJSDoc()) && isJSDocLinkTag(scanner.getTokenValue())); const nameExpression = isMarkdownOrJSDocLink ? undefined : parseJSDocNameReference(); const comments = indent !== undefined && indentText !== undefined ? parseTrailingTagComments(start, getNodePos(), indent, indentText) : undefined; return finishNode(factory.createJSDocSeeTag(tagName, nameExpression, comments), start); } function parseAuthorTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocAuthorTag { const commentStart = getNodePos(); const textOnly = parseAuthorNameAndEmail(); let commentEnd = scanner.getStartPos(); const comments = parseTrailingTagComments(start, commentEnd, indent, indentText); if (!comments) { commentEnd = scanner.getStartPos(); } const allParts = typeof comments !== "string" ? createNodeArray(concatenate([finishNode(textOnly, commentStart, commentEnd)], comments) as JSDocComment[], commentStart) // cast away readonly : textOnly.text + comments; return finishNode(factory.createJSDocAuthorTag(tagName, allParts), start); } function parseAuthorNameAndEmail(): JSDocText { const comments: string[] = []; let inEmail = false; let token = scanner.getToken(); while (token !== SyntaxKind.EndOfFileToken && token !== SyntaxKind.NewLineTrivia) { if (token === SyntaxKind.LessThanToken) { inEmail = true; } else if (token === SyntaxKind.AtToken && !inEmail) { break; } else if (token === SyntaxKind.GreaterThanToken && inEmail) { comments.push(scanner.getTokenText()); scanner.setTextPos(scanner.getTokenPos() + 1); break; } comments.push(scanner.getTokenText()); token = nextTokenJSDoc(); } return factory.createJSDocText(comments.join("")); } function parseImplementsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocImplementsTag { const className = parseExpressionWithTypeArgumentsForAugments(); return finishNode(factory.createJSDocImplementsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); } function parseAugmentsTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocAugmentsTag { const className = parseExpressionWithTypeArgumentsForAugments(); return finishNode(factory.createJSDocAugmentsTag(tagName, className, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); } function parseExpressionWithTypeArgumentsForAugments(): ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression } { const usedBrace = parseOptional(SyntaxKind.OpenBraceToken); const pos = getNodePos(); const expression = parsePropertyAccessEntityNameExpression(); const typeArguments = tryParseTypeArguments(); const node = factory.createExpressionWithTypeArguments(expression, typeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression }; const res = finishNode(node, pos); if (usedBrace) { parseExpected(SyntaxKind.CloseBraceToken); } return res; } function parsePropertyAccessEntityNameExpression() { const pos = getNodePos(); let node: Identifier | PropertyAccessEntityNameExpression = parseJSDocIdentifierName(); while (parseOptional(SyntaxKind.DotToken)) { const name = parseJSDocIdentifierName(); node = finishNode(factory.createPropertyAccessExpression(node, name), pos) as PropertyAccessEntityNameExpression; } return node; } function parseSimpleTag(start: number, createTag: (tagName: Identifier | undefined, comment?: string | NodeArray) => JSDocTag, tagName: Identifier, margin: number, indentText: string): JSDocTag { return finishNode(createTag(tagName, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); } function parseThisTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocThisTag { const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); skipWhitespace(); return finishNode(factory.createJSDocThisTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); } function parseEnumTag(start: number, tagName: Identifier, margin: number, indentText: string): JSDocEnumTag { const typeExpression = parseJSDocTypeExpression(/*mayOmitBraces*/ true); skipWhitespace(); return finishNode(factory.createJSDocEnumTag(tagName, typeExpression, parseTrailingTagComments(start, getNodePos(), margin, indentText)), start); } function parseTypedefTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTypedefTag { let typeExpression: JSDocTypeExpression | JSDocTypeLiteral | undefined = tryParseTypeExpression(); skipWhitespaceOrAsterisk(); const fullName = parseJSDocTypeNameWithNamespace(); skipWhitespace(); let comment = parseTagComments(indent); let end: number | undefined; if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) { let child: JSDocTypeTag | JSDocPropertyTag | false; let childTypeTag: JSDocTypeTag | undefined; let jsDocPropertyTags: JSDocPropertyTag[] | undefined; let hasChildren = false; while (child = tryParse(() => parseChildPropertyTag(indent))) { hasChildren = true; if (child.kind === SyntaxKind.JSDocTypeTag) { if (childTypeTag) { const lastError = parseErrorAtCurrentToken(Diagnostics.A_JSDoc_typedef_comment_may_not_contain_multiple_type_tags); if (lastError) { addRelatedInfo(lastError, createDetachedDiagnostic(fileName, 0, 0, Diagnostics.The_tag_was_first_specified_here)); } break; } else { childTypeTag = child; } } else { jsDocPropertyTags = append(jsDocPropertyTags, child); } } if (hasChildren) { const isArrayType = typeExpression && typeExpression.type.kind === SyntaxKind.ArrayType; const jsdocTypeLiteral = factory.createJSDocTypeLiteral(jsDocPropertyTags, isArrayType); typeExpression = childTypeTag && childTypeTag.typeExpression && !isObjectOrObjectArrayTypeReference(childTypeTag.typeExpression.type) ? childTypeTag.typeExpression : finishNode(jsdocTypeLiteral, start); end = typeExpression.end; } } // Only include the characters between the name end and the next token if a comment was actually parsed out - otherwise it's just whitespace end = end || comment !== undefined ? getNodePos() : (fullName ?? typeExpression ?? tagName).end; if (!comment) { comment = parseTrailingTagComments(start, end, indent, indentText); } const typedefTag = factory.createJSDocTypedefTag(tagName, typeExpression, fullName, comment); return finishNode(typedefTag, start, end); } function parseJSDocTypeNameWithNamespace(nested?: boolean) { const pos = scanner.getTokenPos(); if (!tokenIsIdentifierOrKeyword(token())) { return undefined; } const typeNameOrNamespaceName = parseJSDocIdentifierName(); if (parseOptional(SyntaxKind.DotToken)) { const body = parseJSDocTypeNameWithNamespace(/*nested*/ true); const jsDocNamespaceNode = factory.createModuleDeclaration( /*modifiers*/ undefined, typeNameOrNamespaceName, body, nested ? NodeFlags.NestedNamespace : undefined ) as JSDocNamespaceDeclaration; return finishNode(jsDocNamespaceNode, pos); } if (nested) { typeNameOrNamespaceName.isInJSDocNamespace = true; } return typeNameOrNamespaceName; } function parseCallbackTagParameters(indent: number) { const pos = getNodePos(); let child: JSDocParameterTag | false; let parameters; while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) { parameters = append(parameters, child); } return createNodeArray(parameters || [], pos); } function parseCallbackTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocCallbackTag { const fullName = parseJSDocTypeNameWithNamespace(); skipWhitespace(); let comment = parseTagComments(indent); const parameters = parseCallbackTagParameters(indent); const returnTag = tryParse(() => { if (parseOptionalJsdoc(SyntaxKind.AtToken)) { const tag = parseTag(indent); if (tag && tag.kind === SyntaxKind.JSDocReturnTag) { return tag as JSDocReturnTag; } } }); const typeExpression = finishNode(factory.createJSDocSignature(/*typeParameters*/ undefined, parameters, returnTag), start); if (!comment) { comment = parseTrailingTagComments(start, getNodePos(), indent, indentText); } const end = comment !== undefined ? getNodePos() : typeExpression.end; return finishNode(factory.createJSDocCallbackTag(tagName, typeExpression, fullName, comment), start, end); } function escapedTextsEqual(a: EntityName, b: EntityName): boolean { while (!ts.isIdentifier(a) || !ts.isIdentifier(b)) { if (!ts.isIdentifier(a) && !ts.isIdentifier(b) && a.right.escapedText === b.right.escapedText) { a = a.left; b = b.left; } else { return false; } } return a.escapedText === b.escapedText; } function parseChildPropertyTag(indent: number) { return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false; } function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { let canParseTag = true; let seenAsterisk = false; while (true) { switch (nextTokenJSDoc()) { case SyntaxKind.AtToken: if (canParseTag) { const child = tryParseChildTag(target, indent); if (child && (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) && target !== PropertyLikeParse.CallbackParameter && name && (ts.isIdentifier(child.name) || !escapedTextsEqual(name, child.name.left))) { return false; } return child; } seenAsterisk = false; break; case SyntaxKind.NewLineTrivia: canParseTag = true; seenAsterisk = false; break; case SyntaxKind.AsteriskToken: if (seenAsterisk) { canParseTag = false; } seenAsterisk = true; break; case SyntaxKind.Identifier: canParseTag = false; break; case SyntaxKind.EndOfFileToken: return false; } } } function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false { Debug.assert(token() === SyntaxKind.AtToken); const start = scanner.getStartPos(); nextTokenJSDoc(); const tagName = parseJSDocIdentifierName(); skipWhitespace(); let t: PropertyLikeParse; switch (tagName.escapedText) { case "type": return target === PropertyLikeParse.Property && parseTypeTag(start, tagName); case "prop": case "property": t = PropertyLikeParse.Property; break; case "arg": case "argument": case "param": t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter; break; default: return false; } if (!(target & t)) { return false; } return parseParameterOrPropertyTag(start, tagName, target, indent); } function parseTemplateTagTypeParameter() { const typeParameterPos = getNodePos(); const isBracketed = parseOptionalJsdoc(SyntaxKind.OpenBracketToken); if (isBracketed) { skipWhitespace(); } const name = parseJSDocIdentifierName(Diagnostics.Unexpected_token_A_type_parameter_name_was_expected_without_curly_braces); let defaultType: TypeNode | undefined; if (isBracketed) { skipWhitespace(); parseExpected(SyntaxKind.EqualsToken); defaultType = doInsideOfContext(NodeFlags.JSDoc, parseJSDocType); parseExpected(SyntaxKind.CloseBracketToken); } if (nodeIsMissing(name)) { return undefined; } return finishNode(factory.createTypeParameterDeclaration(/*modifiers*/ undefined, name, /*constraint*/ undefined, defaultType), typeParameterPos); } function parseTemplateTagTypeParameters() { const pos = getNodePos(); const typeParameters = []; do { skipWhitespace(); const node = parseTemplateTagTypeParameter(); if (node !== undefined) { typeParameters.push(node); } skipWhitespaceOrAsterisk(); } while (parseOptionalJsdoc(SyntaxKind.CommaToken)); return createNodeArray(typeParameters, pos); } function parseTemplateTag(start: number, tagName: Identifier, indent: number, indentText: string): JSDocTemplateTag { // The template tag looks like one of the following: // @template T,U,V // @template {Constraint} T // // According to the [closure docs](https://github.com/google/closure-compiler/wiki/Generic-Types#multiple-bounded-template-types): // > Multiple bounded generics cannot be declared on the same line. For the sake of clarity, if multiple templates share the same // > type bound they must be declared on separate lines. // // TODO: Determine whether we should enforce this in the checker. // TODO: Consider moving the `constraint` to the first type parameter as we could then remove `getEffectiveConstraintOfTypeParameter`. // TODO: Consider only parsing a single type parameter if there is a constraint. const constraint = token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; const typeParameters = parseTemplateTagTypeParameters(); return finishNode(factory.createJSDocTemplateTag(tagName, constraint, typeParameters, parseTrailingTagComments(start, getNodePos(), indent, indentText)), start); } function parseOptionalJsdoc(t: JSDocSyntaxKind): boolean { if (token() === t) { nextTokenJSDoc(); return true; } return false; } function parseJSDocEntityName(): EntityName { let entity: EntityName = parseJSDocIdentifierName(); if (parseOptional(SyntaxKind.OpenBracketToken)) { parseExpected(SyntaxKind.CloseBracketToken); // Note that y[] is accepted as an entity name, but the postfix brackets are not saved for checking. // Technically usejsdoc.org requires them for specifying a property of a type equivalent to Array<{ x: ...}> // but it's not worth it to enforce that restriction. } while (parseOptional(SyntaxKind.DotToken)) { const name = parseJSDocIdentifierName(); if (parseOptional(SyntaxKind.OpenBracketToken)) { parseExpected(SyntaxKind.CloseBracketToken); } entity = createQualifiedName(entity, name); } return entity; } function parseJSDocIdentifierName(message?: DiagnosticMessage): Identifier { if (!tokenIsIdentifierOrKeyword(token())) { return createMissingNode(SyntaxKind.Identifier, /*reportAtCurrentPosition*/ !message, message || Diagnostics.Identifier_expected); } identifierCount++; const pos = scanner.getTokenPos(); const end = scanner.getTextPos(); const originalKeywordKind = token(); const text = internIdentifier(scanner.getTokenValue()); const result = finishNode(factory.createIdentifier(text, /*typeArguments*/ undefined, originalKeywordKind), pos, end); nextTokenJSDoc(); return result; } } } } namespace 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, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator); } // 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 unusable 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. const incrementalSourceFile = sourceFile as Node as IncrementalNode; Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed); incrementalSourceFile.hasBeenIncrementallyParsed = true; Parser.fixupParentReferences(incrementalSourceFile); const oldText = sourceFile.text; const syntaxCursor = createSyntaxCursor(sourceFile); // Make the actual change larger so that we know to reparse anything whose lookahead // might have intersected the change. const 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). const 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. const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator); result.commentDirectives = getNewCommentDirectives( sourceFile.commentDirectives, result.commentDirectives, changeRange.span.start, textSpanEnd(changeRange.span), delta, oldText, newText, aggressiveChecks ); result.impliedNodeFormat = sourceFile.impliedNodeFormat; return result; } function getNewCommentDirectives( oldDirectives: CommentDirective[] | undefined, newDirectives: CommentDirective[] | undefined, changeStart: number, changeRangeOldEnd: number, delta: number, oldText: string, newText: string, aggressiveChecks: boolean ): CommentDirective[] | undefined { if (!oldDirectives) return newDirectives; let commentDirectives: CommentDirective[] | undefined; let addedNewlyScannedDirectives = false; for (const directive of oldDirectives) { const { range, type } = directive; // Range before the change if (range.end < changeStart) { commentDirectives = append(commentDirectives, directive); } else if (range.pos > changeRangeOldEnd) { addNewlyScannedDirectives(); // Node is entirely past the change range. We need to move both its pos and // end, forward or backward appropriately. const updatedDirective: CommentDirective = { range: { pos: range.pos + delta, end: range.end + delta }, type }; commentDirectives = append(commentDirectives, updatedDirective); if (aggressiveChecks) { Debug.assert(oldText.substring(range.pos, range.end) === newText.substring(updatedDirective.range.pos, updatedDirective.range.end)); } } // Ignore ranges that fall in change range } addNewlyScannedDirectives(); return commentDirectives; function addNewlyScannedDirectives() { if (addedNewlyScannedDirectives) return; addedNewlyScannedDirectives = true; if (!commentDirectives) { commentDirectives = newDirectives; } else if (newDirectives) { commentDirectives.push(...newDirectives); } } } function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) { if (isArray) { visitArray(element as IncrementalNodeArray); } else { visitNode(element as IncrementalNode); } return; function visitNode(node: IncrementalNode) { let text = ""; if (aggressiveChecks && shouldCheckNode(node)) { 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; } setTextRangePosEnd(node, node.pos + delta, node.end + delta); if (aggressiveChecks && shouldCheckNode(node)) { Debug.assert(text === newText.substring(node.pos, node.end)); } forEachChild(node, visitNode, visitArray); if (hasJSDocNodes(node)) { for (const jsDocComment of node.jsDoc!) { visitNode(jsDocComment as Node as IncrementalNode); } } checkNodePositions(node, aggressiveChecks); } function visitArray(array: IncrementalNodeArray) { array._children = undefined; setTextRangePosEnd(array, array.pos + delta, array.end + delta); for (const 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 // children 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 that 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. const 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 that 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. const end = element.end >= changeRangeOldEnd ? // Element ends after the change range. Always adjust the end pos. element.end + delta : // 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. Math.min(element.end, changeRangeNewEnd); Debug.assert(pos <= end); if (element.parent) { Debug.assertGreaterThanOrEqual(pos, element.parent.pos); Debug.assertLessThanOrEqual(end, element.parent.end); } setTextRangePosEnd(element, pos, end); } function checkNodePositions(node: Node, aggressiveChecks: boolean) { if (aggressiveChecks) { let pos = node.pos; const visitNode = (child: Node) => { Debug.assert(child.pos >= pos); pos = child.end; }; if (hasJSDocNodes(node)) { for (const jsDocComment of node.jsDoc!) { visitNode(jsDocComment); } } forEachChild(node, visitNode); 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. const 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); if (hasJSDocNodes(child)) { for (const jsDocComment of child.jsDoc!) { visitNode(jsDocComment as Node as IncrementalNode); } } 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. const 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 (const node of array) { if (!node.virtual) { 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. const 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++) { const nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start); Debug.assert(nearestNode.pos <= start); const position = nearestNode.pos; start = Math.max(0, position - 1); } const finalSpan = createTextSpanFromBounds(start, textSpanEnd(changeRange.span)); const 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 | undefined; forEachChild(sourceFile, visit); if (lastNodeEntirelyBeforePosition) { const lastChildOfLastEntireNodeBeforePosition = getLastDescendant(lastNodeEntirelyBeforePosition); if (lastChildOfLastEntireNodeBeforePosition.pos > bestResult.pos) { bestResult = lastChildOfLastEntireNodeBeforePosition; } } return bestResult; function getLastDescendant(node: Node): Node { while (true) { const lastChild = getLastChild(node); if (lastChild) { node = lastChild; } else { return node; } } } 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) // // ? $ : <...> <...> // // 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) { const oldText = sourceFile.text; if (textChangeRange) { Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length); if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) { const oldTextPrefix = oldText.substr(0, textChangeRange.span.start); const newTextPrefix = newText.substr(0, textChangeRange.span.start); Debug.assert(oldTextPrefix === newTextPrefix); const oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length); const newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length); Debug.assert(oldTextSuffix === newTextSuffix); } } } interface IncrementalElement extends ReadonlyTextRange { readonly parent: Node; intersectsChange: boolean; length?: number; _children: Node[] | undefined; } export interface IncrementalNode extends Node, IncrementalElement { hasBeenIncrementallyParsed: boolean; } interface IncrementalNodeArray extends NodeArray, 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; } export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { let currentArray: NodeArray = 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 current as IncrementalNode; } }; // 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 proceed any further in the search. return true; } // position wasn't in this node, have to keep searching. return false; } function visitArray(array: NodeArray) { 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; i < array.length; i++) { const 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 } } /** @internal */ export function isDeclarationFileName(fileName: string): boolean { return fileExtensionIsOneOf(fileName, supportedDeclarationExtensions); } /** @internal */ export interface PragmaContext { languageVersion: ScriptTarget; pragmas?: PragmaMap; checkJsDirective?: CheckJsDirective; referencedFiles: FileReference[]; typeReferenceDirectives: FileReference[]; libReferenceDirectives: FileReference[]; amdDependencies: AmdDependency[]; hasNoDefaultLib?: boolean; moduleName?: string; } function parseResolutionMode(mode: string | undefined, pos: number, end: number, reportDiagnostic: PragmaDiagnosticReporter): ModuleKind.ESNext | ModuleKind.CommonJS | undefined { if (!mode) { return undefined; } if (mode === "import") { return ModuleKind.ESNext; } if (mode === "require") { return ModuleKind.CommonJS; } reportDiagnostic(pos, end - pos, Diagnostics.resolution_mode_should_be_either_require_or_import); return undefined; } /** @internal */ export function processCommentPragmas(context: PragmaContext, sourceText: string): void { const pragmas: PragmaPseudoMapEntry[] = []; for (const range of getLeadingCommentRanges(sourceText, 0) || emptyArray) { const comment = sourceText.substring(range.pos, range.end); extractPragmas(pragmas, range, comment); } context.pragmas = new Map() as PragmaMap; for (const pragma of pragmas) { if (context.pragmas.has(pragma.name)) { const currentValue = context.pragmas.get(pragma.name); if (currentValue instanceof Array) { currentValue.push(pragma.args); } else { context.pragmas.set(pragma.name, [currentValue, pragma.args]); } continue; } context.pragmas.set(pragma.name, pragma.args); } } /** @internal */ export type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void; /** @internal */ export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void { context.checkJsDirective = undefined; context.referencedFiles = []; context.typeReferenceDirectives = []; context.libReferenceDirectives = []; context.amdDependencies = []; context.hasNoDefaultLib = false; context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217 // TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to // key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :( switch (key) { case "reference": { const referencedFiles = context.referencedFiles; const typeReferenceDirectives = context.typeReferenceDirectives; const libReferenceDirectives = context.libReferenceDirectives; forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => { const { types, lib, path, ["resolution-mode"]: res } = arg.arguments; if (arg.arguments["no-default-lib"]) { context.hasNoDefaultLib = true; } else if (types) { const parsed = parseResolutionMode(res, types.pos, types.end, reportDiagnostic); typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value, ...(parsed ? { resolutionMode: parsed } : {}) }); } else if (lib) { libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value }); } else if (path) { referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value }); } else { reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax); } }); break; } case "amd-dependency": { context.amdDependencies = map( toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][], x => ({ name: x.arguments.name, path: x.arguments.path })); break; } case "amd-module": { if (entryOrList instanceof Array) { for (const entry of entryOrList) { if (context.moduleName) { // TODO: It's probably fine to issue this diagnostic on all instances of the pragma reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, Diagnostics.An_AMD_module_cannot_have_multiple_name_assignments); } context.moduleName = (entry as PragmaPseudoMap["amd-module"]).arguments.name; } } else { context.moduleName = (entryOrList as PragmaPseudoMap["amd-module"]).arguments.name; } break; } case "ts-nocheck": case "ts-check": { // _last_ of either nocheck or check in a file is the "winner" forEach(toArray(entryOrList), entry => { if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) { context.checkJsDirective = { enabled: key === "ts-check", end: entry.range.end, pos: entry.range.pos }; } }); break; } case "jsx": case "jsxfrag": case "jsximportsource": case "jsxruntime": return; // Accessed directly default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future? } }); } const namedArgRegExCache = new Map(); function getNamedArgRegEx(name: string): RegExp { if (namedArgRegExCache.has(name)) { return namedArgRegExCache.get(name)!; } const result = new RegExp(`(\\s${name}\\s*=\\s*)(?:(?:'([^']*)')|(?:"([^"]*)"))`, "im"); namedArgRegExCache.set(name, result); return result; } const tripleSlashXMLCommentStartRegEx = /^\/\/\/\s*<(\S+)\s.*?\/>/im; const singleLinePragmaRegEx = /^\/\/\/?\s*@(\S+)\s*(.*)\s*$/im; function extractPragmas(pragmas: PragmaPseudoMapEntry[], range: CommentRange, text: string) { const tripleSlash = range.kind === SyntaxKind.SingleLineCommentTrivia && tripleSlashXMLCommentStartRegEx.exec(text); if (tripleSlash) { const name = tripleSlash[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so the below check to make it safe typechecks const pragma = commentPragmas[name] as PragmaDefinition; if (!pragma || !(pragma.kind! & PragmaKindFlags.TripleSlashXML)) { return; } if (pragma.args) { const argument: {[index: string]: string | {value: string, pos: number, end: number}} = {}; for (const arg of pragma.args) { const matcher = getNamedArgRegEx(arg.name); const matchResult = matcher.exec(text); if (!matchResult && !arg.optional) { return; // Missing required argument, don't parse } else if (matchResult) { const value = matchResult[2] || matchResult[3]; if (arg.captureSpan) { const startPos = range.pos + matchResult.index + matchResult[1].length + 1; argument[arg.name] = { value, pos: startPos, end: startPos + value.length }; } else { argument[arg.name] = value; } } } pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); } else { pragmas.push({ name, args: { arguments: {}, range } } as PragmaPseudoMapEntry); } return; } const singleLine = range.kind === SyntaxKind.SingleLineCommentTrivia && singleLinePragmaRegEx.exec(text); if (singleLine) { return addPragmaForMatch(pragmas, range, PragmaKindFlags.SingleLine, singleLine); } if (range.kind === SyntaxKind.MultiLineCommentTrivia) { const multiLinePragmaRegEx = /@(\S+)(\s+.*)?$/gim; // Defined inline since it uses the "g" flag, which keeps a persistent index (for iterating) let multiLineMatch: RegExpExecArray | null; while (multiLineMatch = multiLinePragmaRegEx.exec(text)) { addPragmaForMatch(pragmas, range, PragmaKindFlags.MultiLine, multiLineMatch); } } } function addPragmaForMatch(pragmas: PragmaPseudoMapEntry[], range: CommentRange, kind: PragmaKindFlags, match: RegExpExecArray) { if (!match) return; const name = match[1].toLowerCase() as keyof PragmaPseudoMap; // Technically unsafe cast, but we do it so they below check to make it safe typechecks const pragma = commentPragmas[name] as PragmaDefinition; if (!pragma || !(pragma.kind! & kind)) { return; } const args = match[2]; // Split on spaces and match up positionally with definition const argument = getNamedPragmaArguments(pragma, args); if (argument === "fail") return; // Missing required argument, fail to parse it pragmas.push({ name, args: { arguments: argument, range } } as PragmaPseudoMapEntry); return; } function getNamedPragmaArguments(pragma: PragmaDefinition, text: string | undefined): {[index: string]: string} | "fail" { if (!text) return {}; if (!pragma.args) return {}; const args = trimString(text).split(/\s+/); const argMap: {[index: string]: string} = {}; for (let i = 0; i < pragma.args.length; i++) { const argument = pragma.args[i]; if (!args[i] && !argument.optional) { return "fail"; } if (argument.captureSpan) { return Debug.fail("Capture spans not yet implemented for non-xml pragmas"); } argMap[argument.name] = args[i]; } return argMap; } /** @internal */ export function tagNamesAreEquivalent(lhs: JsxTagNameExpression, rhs: JsxTagNameExpression): boolean { if (lhs.kind !== rhs.kind) { return false; } if (lhs.kind === SyntaxKind.Identifier) { return lhs.escapedText === (rhs as Identifier).escapedText; } if (lhs.kind === SyntaxKind.ThisKeyword) { return true; } // If we are at this statement then we must have PropertyAccessExpression and because tag name in Jsx element can only // take forms of JsxTagNameExpression which includes an identifier, "this" expression, or another propertyAccessExpression // it is safe to case the expression property as such. See parseJsxElementName for how we parse tag name in Jsx element return (lhs as PropertyAccessExpression).name.escapedText === (rhs as PropertyAccessExpression).name.escapedText && tagNamesAreEquivalent((lhs as PropertyAccessExpression).expression as JsxTagNameExpression, (rhs as PropertyAccessExpression).expression as JsxTagNameExpression); }